gitarsenal-cli 1.6.6 → 1.6.8

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.6",
3
+ "version": "1.6.8",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -60,19 +60,19 @@ except (ImportError, ValueError) as e:
60
60
  # os.environ["MODAL_TOKEN_SECRET"] = TOKEN_SECRET
61
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.")
@@ -234,5 +234,6 @@ try:
234
234
 
235
235
  except Exception as e:
236
236
  # print(f"❌ Error testing Modal authentication: {e}")
237
+ print("")
237
238
 
238
239
  # print("\n✅ Done fixing Modal token. Please try your command again.")
@@ -1184,12 +1184,11 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1184
1184
  try:
1185
1185
  print("📦 Building SSH-enabled image...")
1186
1186
  ssh_image = (
1187
- # modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
1188
- modal.Image.debian_slim()
1187
+ modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
1189
1188
  .apt_install(
1190
1189
  "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
1191
1190
  "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
1192
- "gpg", "ca-certificates", "software-properties-common"
1191
+ "gpg", "ca-certificates", "software-properties-common", "nvtop"
1193
1192
  )
1194
1193
  .pip_install("uv", "modal", "requests", "openai") # Fast Python package installer and Modal
1195
1194
  .run_commands(
@@ -1198,6 +1197,9 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1198
1197
  "mkdir -p /root/.ssh",
1199
1198
  "chmod 700 /root/.ssh",
1200
1199
 
1200
+ # Generate SSH host keys
1201
+ "ssh-keygen -A",
1202
+
1201
1203
  # Configure SSH server
1202
1204
  "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
1203
1205
  "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
@@ -1207,8 +1209,9 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1207
1209
  "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
1208
1210
  "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
1209
1211
 
1210
- # Generate SSH host keys
1211
- "ssh-keygen -A",
1212
+ # Set up CUDA environment
1213
+ "echo 'export PATH=/usr/local/cuda/bin:$PATH' >> /root/.bashrc",
1214
+ "echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> /root/.bashrc",
1212
1215
 
1213
1216
  # Set up a nice bash prompt
1214
1217
  "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
@@ -1224,7 +1227,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1224
1227
  if volume:
1225
1228
  volumes_config[volume_mount_path] = volume
1226
1229
 
1227
- # Define the SSH container function
1230
+ # Define the SSH container function - simplified like the example
1228
1231
  @app.function(
1229
1232
  image=ssh_image,
1230
1233
  timeout=timeout_minutes * 60, # Convert to seconds
@@ -1234,7 +1237,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1234
1237
  serialized=True,
1235
1238
  volumes=volumes_config if volumes_config else None,
1236
1239
  )
1237
- def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None):
1240
+ def start_ssh():
1238
1241
  """Start SSH container with password authentication and optional setup."""
1239
1242
  import subprocess
1240
1243
  import time
@@ -1244,14 +1247,25 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1244
1247
  subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
1245
1248
 
1246
1249
  # Set OpenAI API key if provided
1250
+ openai_api_key = os.environ.get("OPENAI_API_KEY")
1247
1251
  if openai_api_key:
1248
1252
  os.environ['OPENAI_API_KEY'] = openai_api_key
1249
1253
  print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
1250
1254
  else:
1251
1255
  print("⚠️ No OpenAI API key provided to container")
1252
1256
 
1253
- # Start SSH service
1254
- subprocess.run(["service", "ssh", "start"], check=True)
1257
+ # Start SSH service using Popen (non-blocking) like in the example
1258
+ subprocess.Popen(["/usr/sbin/sshd", "-D"])
1259
+ time.sleep(2) # Give SSH time to start
1260
+
1261
+ # Test CUDA setup
1262
+ try:
1263
+ print("🔧 Testing CUDA setup...")
1264
+ subprocess.run(["nvidia-smi"], check=True)
1265
+ subprocess.run(["nvcc", "--version"], check=True)
1266
+ print("✅ CUDA setup verified")
1267
+ except subprocess.CalledProcessError as e:
1268
+ print(f"⚠️ CUDA test failed: {e}")
1255
1269
 
1256
1270
  # Clone repository if provided
1257
1271
  if repo_url:
@@ -1379,43 +1393,28 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1379
1393
  print(f"🔍 Analyzing directory structure after failed cd command...")
1380
1394
  subprocess.run("pwd && ls -la && echo '--- Parent directory ---' && ls -la ..", shell=True, check=False)
1381
1395
 
1382
- # Create SSH tunnel
1383
- with modal.forward(22, unencrypted=True) as tunnel:
1384
- host, port = tunnel.tcp_socket
1396
+ # Forward SSH port and keep container alive - exactly like the example
1397
+ with modal.forward(port=22, unencrypted=True) as tunnel:
1398
+ hostname, port = tunnel.tcp_socket
1385
1399
 
1386
1400
  print("\n" + "=" * 80)
1387
1401
  print("🎉 SSH CONTAINER IS READY!")
1388
1402
  print("=" * 80)
1389
- print(f"🌐 SSH Host: {host}")
1390
- print(f"🔌 SSH Port: {port}")
1391
- print(f"👤 Username: root")
1392
- print(f"🔐 Password: {ssh_password}")
1393
- print()
1394
- print("🔗 CONNECT USING THIS COMMAND:")
1395
- print(f"ssh -p {port} root@{host}")
1403
+ print(f"SSH: ssh -p {port} root@{hostname}")
1404
+ print(f"Password: {ssh_password}")
1396
1405
  print("=" * 80)
1397
1406
 
1398
- # Keep the container running
1407
+ # Keep alive - simplified like the example
1399
1408
  while True:
1400
- time.sleep(30)
1401
- # Check if SSH service is still running
1402
- try:
1403
- subprocess.run(["service", "ssh", "status"], check=True,
1404
- capture_output=True)
1405
- except subprocess.CalledProcessError:
1406
- print("⚠️ SSH service stopped, restarting...")
1407
- subprocess.run(["service", "ssh", "start"], check=True)
1409
+ time.sleep(60)
1408
1410
 
1409
1411
  # Run the container
1410
1412
  try:
1411
1413
  print("⏳ Starting container... This may take 1-2 minutes...")
1412
1414
 
1413
- # Start the container in a new thread to avoid blocking
1414
- with modal.enable_output():
1415
- with app.run():
1416
- # Get the API key from environment
1417
- api_key = os.environ.get("OPENAI_API_KEY")
1418
- ssh_container_function.remote(ssh_password, repo_url, repo_name, setup_commands, api_key)
1415
+ # Start the container - simplified like the example
1416
+ with app.run():
1417
+ start_ssh.remote()
1419
1418
 
1420
1419
  # Clean up Modal token after container is successfully created
1421
1420
  cleanup_modal_token()
@@ -1184,12 +1184,11 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1184
1184
  try:
1185
1185
  print("📦 Building SSH-enabled image...")
1186
1186
  ssh_image = (
1187
- # modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
1188
- modal.Image.debian_slim()
1187
+ modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
1189
1188
  .apt_install(
1190
1189
  "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
1191
1190
  "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
1192
- "gpg", "ca-certificates", "software-properties-common"
1191
+ "gpg", "ca-certificates", "software-properties-common", "nvtop"
1193
1192
  )
1194
1193
  .pip_install("uv", "modal", "requests", "openai") # Fast Python package installer and Modal
1195
1194
  .run_commands(
@@ -1198,6 +1197,9 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1198
1197
  "mkdir -p /root/.ssh",
1199
1198
  "chmod 700 /root/.ssh",
1200
1199
 
1200
+ # Generate SSH host keys
1201
+ "ssh-keygen -A",
1202
+
1201
1203
  # Configure SSH server
1202
1204
  "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
1203
1205
  "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
@@ -1207,8 +1209,9 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1207
1209
  "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
1208
1210
  "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
1209
1211
 
1210
- # Generate SSH host keys
1211
- "ssh-keygen -A",
1212
+ # Set up CUDA environment
1213
+ "echo 'export PATH=/usr/local/cuda/bin:$PATH' >> /root/.bashrc",
1214
+ "echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> /root/.bashrc",
1212
1215
 
1213
1216
  # Set up a nice bash prompt
1214
1217
  "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
@@ -1224,7 +1227,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1224
1227
  if volume:
1225
1228
  volumes_config[volume_mount_path] = volume
1226
1229
 
1227
- # Define the SSH container function
1230
+ # Define the SSH container function - simplified like the example
1228
1231
  @app.function(
1229
1232
  image=ssh_image,
1230
1233
  timeout=timeout_minutes * 60, # Convert to seconds
@@ -1234,7 +1237,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1234
1237
  serialized=True,
1235
1238
  volumes=volumes_config if volumes_config else None,
1236
1239
  )
1237
- def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None):
1240
+ def start_ssh():
1238
1241
  """Start SSH container with password authentication and optional setup."""
1239
1242
  import subprocess
1240
1243
  import time
@@ -1244,14 +1247,25 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1244
1247
  subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
1245
1248
 
1246
1249
  # Set OpenAI API key if provided
1250
+ openai_api_key = os.environ.get("OPENAI_API_KEY")
1247
1251
  if openai_api_key:
1248
1252
  os.environ['OPENAI_API_KEY'] = openai_api_key
1249
1253
  print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
1250
1254
  else:
1251
1255
  print("⚠️ No OpenAI API key provided to container")
1252
1256
 
1253
- # Start SSH service
1254
- subprocess.run(["service", "ssh", "start"], check=True)
1257
+ # Start SSH service using Popen (non-blocking) like in the example
1258
+ subprocess.Popen(["/usr/sbin/sshd", "-D"])
1259
+ time.sleep(2) # Give SSH time to start
1260
+
1261
+ # Test CUDA setup
1262
+ try:
1263
+ print("🔧 Testing CUDA setup...")
1264
+ subprocess.run(["nvidia-smi"], check=True)
1265
+ subprocess.run(["nvcc", "--version"], check=True)
1266
+ print("✅ CUDA setup verified")
1267
+ except subprocess.CalledProcessError as e:
1268
+ print(f"⚠️ CUDA test failed: {e}")
1255
1269
 
1256
1270
  # Clone repository if provided
1257
1271
  if repo_url:
@@ -1379,43 +1393,28 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1379
1393
  print(f"🔍 Analyzing directory structure after failed cd command...")
1380
1394
  subprocess.run("pwd && ls -la && echo '--- Parent directory ---' && ls -la ..", shell=True, check=False)
1381
1395
 
1382
- # Create SSH tunnel
1383
- with modal.forward(22, unencrypted=True) as tunnel:
1384
- host, port = tunnel.tcp_socket
1396
+ # Forward SSH port and keep container alive - exactly like the example
1397
+ with modal.forward(port=22, unencrypted=True) as tunnel:
1398
+ hostname, port = tunnel.tcp_socket
1385
1399
 
1386
1400
  print("\n" + "=" * 80)
1387
1401
  print("🎉 SSH CONTAINER IS READY!")
1388
1402
  print("=" * 80)
1389
- print(f"🌐 SSH Host: {host}")
1390
- print(f"🔌 SSH Port: {port}")
1391
- print(f"👤 Username: root")
1392
- print(f"🔐 Password: {ssh_password}")
1393
- print()
1394
- print("🔗 CONNECT USING THIS COMMAND:")
1395
- print(f"ssh -p {port} root@{host}")
1403
+ print(f"SSH: ssh -p {port} root@{hostname}")
1404
+ print(f"Password: {ssh_password}")
1396
1405
  print("=" * 80)
1397
1406
 
1398
- # Keep the container running
1407
+ # Keep alive - simplified like the example
1399
1408
  while True:
1400
- time.sleep(30)
1401
- # Check if SSH service is still running
1402
- try:
1403
- subprocess.run(["service", "ssh", "status"], check=True,
1404
- capture_output=True)
1405
- except subprocess.CalledProcessError:
1406
- print("⚠️ SSH service stopped, restarting...")
1407
- subprocess.run(["service", "ssh", "start"], check=True)
1409
+ time.sleep(60)
1408
1410
 
1409
1411
  # Run the container
1410
1412
  try:
1411
1413
  print("⏳ Starting container... This may take 1-2 minutes...")
1412
1414
 
1413
- # Start the container in a new thread to avoid blocking
1414
- with modal.enable_output():
1415
- with app.run():
1416
- # Get the API key from environment
1417
- api_key = os.environ.get("OPENAI_API_KEY")
1418
- ssh_container_function.remote(ssh_password, repo_url, repo_name, setup_commands, api_key)
1415
+ # Start the container - simplified like the example
1416
+ with app.run():
1417
+ start_ssh.remote()
1419
1418
 
1420
1419
  # Clean up Modal token after container is successfully created
1421
1420
  cleanup_modal_token()
@@ -1,148 +0,0 @@
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())