gitarsenal-cli 1.1.25 → 1.2.1

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.1.25",
3
+ "version": "1.2.1",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -2181,113 +2181,17 @@ def ssh_container_function(ssh_password, repo_url=None, repo_name=None, setup_co
2181
2181
  subprocess.run(["service", "ssh", "start"], check=True)
2182
2182
 
2183
2183
  # Now modify the create_modal_ssh_container function to use the standalone ssh_container_function
2184
-
2185
2184
  def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_commands=None,
2186
2185
  volume_name=None, timeout_minutes=60, ssh_password=None):
2187
- """Create a Modal SSH container with GPU support and tunneling"""
2186
+ """Create a Modal SSH container with GPU support and proper tunneling"""
2188
2187
 
2189
- # Check if Modal is authenticated
2188
+ # Check Modal authentication
2190
2189
  try:
2191
- # Print all environment variables for debugging
2192
- print("šŸ” DEBUG: Checking environment variables")
2193
- modal_token_id = os.environ.get("MODAL_TOKEN_ID")
2194
- modal_token = os.environ.get("MODAL_TOKEN")
2195
- print(f"šŸ” MODAL_TOKEN_ID exists: {'Yes' if modal_token_id else 'No'}")
2196
- print(f"šŸ” MODAL_TOKEN exists: {'Yes' if modal_token else 'No'}")
2197
- if modal_token_id:
2198
- print(f"šŸ” MODAL_TOKEN_ID length: {len(modal_token_id)}")
2199
- if modal_token:
2200
- print(f"šŸ” MODAL_TOKEN length: {len(modal_token)}")
2201
-
2202
- # Try to access Modal token to check authentication
2203
- try:
2204
- # Check if token is set in environment
2205
- modal_token_id = os.environ.get("MODAL_TOKEN_ID")
2206
- if not modal_token_id:
2207
- print("āš ļø MODAL_TOKEN_ID not found in environment.")
2208
- # Try to get from MODAL_TOKEN
2209
- modal_token = os.environ.get("MODAL_TOKEN")
2210
- if modal_token:
2211
- print("āœ… Found token in MODAL_TOKEN environment variable")
2212
- os.environ["MODAL_TOKEN_ID"] = modal_token
2213
- modal_token_id = modal_token
2214
- print(f"āœ… Set MODAL_TOKEN_ID from MODAL_TOKEN (length: {len(modal_token)})")
2215
-
2216
- if modal_token_id:
2217
- print(f"āœ… Modal token found (length: {len(modal_token_id)})")
2218
-
2219
- # Use the comprehensive fix_modal_token script
2220
- try:
2221
- # Execute the fix_modal_token.py script
2222
- import subprocess
2223
- print(f"šŸ”„ Running fix_modal_token.py to set up Modal token...")
2224
- result = subprocess.run(
2225
- ["python", os.path.join(os.path.dirname(__file__), "fix_modal_token.py")],
2226
- capture_output=True,
2227
- text=True
2228
- )
2229
-
2230
- # Print the output
2231
- print(result.stdout)
2232
-
2233
- if result.returncode != 0:
2234
- print(f"āš ļø Warning: fix_modal_token.py exited with code {result.returncode}")
2235
- if result.stderr:
2236
- print(f"Error: {result.stderr}")
2237
-
2238
- print(f"āœ… Modal token setup completed")
2239
- except Exception as e:
2240
- print(f"āš ļø Error running fix_modal_token.py: {e}")
2241
- else:
2242
- print("āŒ No Modal token found in environment variables")
2243
- # Try to get from file as a last resort
2244
- try:
2245
- home_dir = os.path.expanduser("~")
2246
- modal_dir = os.path.join(home_dir, ".modal")
2247
- token_file = os.path.join(modal_dir, "token.json")
2248
- if os.path.exists(token_file):
2249
- print(f"šŸ” Found Modal token file at {token_file}")
2250
- with open(token_file, 'r') as f:
2251
- import json
2252
- token_data = json.load(f)
2253
- if "token_id" in token_data:
2254
- modal_token_id = token_data["token_id"]
2255
- os.environ["MODAL_TOKEN_ID"] = modal_token_id
2256
- os.environ["MODAL_TOKEN"] = modal_token_id
2257
- print(f"āœ… Loaded token from file (length: {len(modal_token_id)})")
2258
- else:
2259
- print("āŒ Token file does not contain token_id")
2260
- else:
2261
- print("āŒ Modal token file not found")
2262
- except Exception as e:
2263
- print(f"āŒ Error loading token from file: {e}")
2264
-
2265
- if not os.environ.get("MODAL_TOKEN_ID"):
2266
- print("āŒ Could not find Modal token in any location")
2267
- return None
2268
-
2269
- except Exception as e:
2270
- print(f"āš ļø Error checking Modal token: {e}")
2271
- # Try to use the token from environment
2272
- modal_token_id = os.environ.get("MODAL_TOKEN_ID")
2273
- modal_token = os.environ.get("MODAL_TOKEN")
2274
- if modal_token_id:
2275
- print(f"šŸ”„ Using MODAL_TOKEN_ID from environment (length: {len(modal_token_id)})")
2276
- elif modal_token:
2277
- print(f"šŸ”„ Using MODAL_TOKEN from environment (length: {len(modal_token)})")
2278
- os.environ["MODAL_TOKEN_ID"] = modal_token
2279
- modal_token_id = modal_token
2280
- else:
2281
- print("āŒ No Modal token available. Cannot proceed.")
2282
- return None
2283
-
2284
- # Set it in both environment variables
2285
- os.environ["MODAL_TOKEN_ID"] = modal_token_id
2286
- os.environ["MODAL_TOKEN"] = modal_token_id
2287
- print("āœ… Set both MODAL_TOKEN_ID and MODAL_TOKEN environment variables")
2190
+ modal.config.get_current_workspace_name()
2191
+ print("āœ… Modal authentication verified")
2288
2192
  except Exception as e:
2289
- print(f"āš ļø Error checking Modal authentication: {e}")
2290
- print("Continuing anyway, but Modal operations may fail")
2193
+ print(f"āŒ Modal authentication failed: {e}")
2194
+ return None
2291
2195
 
2292
2196
  # Generate a unique app name with timestamp to avoid conflicts
2293
2197
  timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
@@ -2326,99 +2230,86 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2326
2230
  print(f"āš ļø Could not setup volume '{volume_name}': {e}")
2327
2231
  print("āš ļø Continuing without persistent volume")
2328
2232
  volume = None
2329
- else:
2330
- # Create a default volume for this session
2331
- default_volume_name = f"ssh-vol-{timestamp}"
2332
- print(f"šŸ“¦ Creating default volume: {default_volume_name}")
2333
- try:
2334
- volume = modal.Volume.from_name(default_volume_name, create_if_missing=True)
2335
- volume_name = default_volume_name
2336
- print(f"āœ… Default volume '{default_volume_name}' created")
2337
- except Exception as e:
2338
- print(f"āš ļø Could not create default volume: {e}")
2339
- print("āš ļø Continuing without persistent volume")
2340
- volume = None
2341
2233
 
2342
- # Print debug info for authentication
2343
- print("šŸ” Modal authentication debug info:")
2344
- modal_token = os.environ.get("MODAL_TOKEN_ID")
2345
- print(f" - MODAL_TOKEN_ID in env: {'Yes' if modal_token else 'No'}")
2346
- print(f" - Token length: {len(modal_token) if modal_token else 'N/A'}")
2234
+ # Create the Modal app
2235
+ app = modal.App(app_name)
2347
2236
 
2348
- # Verify we can create a Modal app
2349
- try:
2350
- print("šŸ” Testing Modal app creation...")
2351
- app = modal.App(app_name)
2352
- print("āœ… Created Modal app successfully")
2353
- except Exception as e:
2354
- print(f"āŒ Error creating Modal app: {e}")
2355
- return None
2356
-
2357
2237
  # Create SSH-enabled image
2358
- try:
2359
- print("šŸ“¦ Building SSH-enabled image...")
2360
- ssh_image = (
2361
- modal.Image.debian_slim()
2362
- .apt_install(
2363
- "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
2364
- "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
2365
- "gpg", "ca-certificates", "software-properties-common"
2366
- )
2367
- .pip_install("uv", "modal") # Fast Python package installer and Modal
2368
- .run_commands(
2369
- # Create SSH directory
2370
- "mkdir -p /var/run/sshd",
2371
- "mkdir -p /root/.ssh",
2372
- "chmod 700 /root/.ssh",
2373
-
2374
- # Configure SSH server
2375
- "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
2376
- "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
2377
- "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
2378
-
2379
- # SSH keep-alive settings
2380
- "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
2381
- "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
2382
-
2383
- # Generate SSH host keys
2384
- "ssh-keygen -A",
2385
-
2386
- # Set up a nice bash prompt
2387
- "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
2388
- )
2238
+ ssh_image = (
2239
+ modal.Image.debian_slim()
2240
+ .apt_install(
2241
+ "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
2242
+ "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
2243
+ "gpg", "ca-certificates", "software-properties-common"
2389
2244
  )
2390
- print("āœ… SSH image built successfully")
2391
- except Exception as e:
2392
- print(f"āŒ Error building SSH image: {e}")
2393
- return None
2394
-
2245
+ .pip_install("uv", "modal")
2246
+ .run_commands(
2247
+ # Create SSH directory
2248
+ "mkdir -p /var/run/sshd",
2249
+ "mkdir -p /root/.ssh",
2250
+ "chmod 700 /root/.ssh",
2251
+
2252
+ # Configure SSH server for password authentication
2253
+ "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
2254
+ "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
2255
+ "sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config",
2256
+ "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
2257
+
2258
+ # SSH keep-alive settings
2259
+ "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
2260
+ "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
2261
+
2262
+ # Allow SSH on port 22
2263
+ "echo 'Port 22' >> /etc/ssh/sshd_config",
2264
+
2265
+ # Generate SSH host keys
2266
+ "ssh-keygen -A",
2267
+
2268
+ # Set up a nice bash prompt
2269
+ "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
2270
+ )
2271
+ )
2272
+
2395
2273
  # Configure volumes if available
2396
2274
  volumes_config = {}
2397
2275
  if volume:
2398
2276
  volumes_config[volume_mount_path] = volume
2399
-
2400
- # Define the SSH container function
2277
+
2278
+ # Define the SSH container function with proper tunnel setup
2401
2279
  @app.function(
2402
2280
  image=ssh_image,
2403
- timeout=timeout_minutes * 60, # Convert to seconds
2281
+ timeout=timeout_minutes * 60,
2404
2282
  gpu=gpu_spec['gpu'],
2405
2283
  cpu=2,
2406
2284
  memory=8192,
2407
- serialized=True,
2408
2285
  volumes=volumes_config if volumes_config else None,
2409
2286
  )
2410
- def ssh_container_function():
2411
- """Start SSH container with password authentication and optional setup."""
2287
+ def ssh_container():
2288
+ """Start SSH container with password authentication and tunnel."""
2412
2289
  import subprocess
2413
2290
  import time
2414
2291
  import os
2415
2292
 
2293
+ print("šŸ”§ Setting up SSH container...")
2294
+
2416
2295
  # Set root password
2296
+ print(f"šŸ” Setting root password...")
2417
2297
  subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
2418
2298
 
2419
2299
  # Start SSH service
2300
+ print("šŸš€ Starting SSH service...")
2420
2301
  subprocess.run(["service", "ssh", "start"], check=True)
2421
2302
 
2303
+ # Verify SSH service is running
2304
+ result = subprocess.run(["service", "ssh", "status"], capture_output=True, text=True)
2305
+ if result.returncode == 0:
2306
+ print("āœ… SSH service is running")
2307
+ else:
2308
+ print("āŒ SSH service failed to start")
2309
+ print(result.stdout)
2310
+ print(result.stderr)
2311
+ return
2312
+
2422
2313
  # Clone repository if provided
2423
2314
  if repo_url:
2424
2315
  repo_name_from_url = repo_name or repo_url.split('/')[-1].replace('.git', '')
@@ -2452,49 +2343,74 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2452
2343
  if e.stderr:
2453
2344
  print(f"āŒ Error: {e.stderr}")
2454
2345
 
2455
- # Create SSH tunnel
2456
- with modal.forward(22) as tunnel:
2457
- host, port = tunnel.host, tunnel.port
2458
-
2346
+ # CRITICAL: Use unencrypted tunnel for SSH (port 22)
2347
+ print("🌐 Creating unencrypted SSH tunnel...")
2348
+
2349
+ # Use Modal's unencrypted tunnel for SSH protocol
2350
+ with modal.forward(22, unencrypted=True) as tunnel:
2459
2351
  print("\n" + "=" * 80)
2460
2352
  print("šŸŽ‰ SSH CONTAINER IS READY!")
2461
2353
  print("=" * 80)
2462
- print(f"🌐 SSH Host: {host}")
2463
- print(f"šŸ”Œ SSH Port: {port}")
2354
+ print(f"🌐 SSH Host: {tunnel.tcp_socket[0]}")
2355
+ print(f"šŸ”Œ SSH Port: {tunnel.tcp_socket[1]}")
2464
2356
  print(f"šŸ‘¤ Username: root")
2465
2357
  print(f"šŸ” Password: {ssh_password}")
2466
2358
  print()
2467
2359
  print("šŸ”— CONNECT USING THIS COMMAND:")
2468
- print(f"ssh -p {port} root@{host}")
2360
+ print(f"ssh -p {tunnel.tcp_socket[1]} root@{tunnel.tcp_socket[0]}")
2469
2361
  print("=" * 80)
2470
2362
 
2471
- # Keep the container running
2472
- while True:
2473
- time.sleep(30)
2474
- # Check if SSH service is still running
2475
- try:
2476
- subprocess.run(["service", "ssh", "status"], check=True,
2477
- capture_output=True)
2478
- except subprocess.CalledProcessError:
2479
- print("āš ļø SSH service stopped, restarting...")
2480
- subprocess.run(["service", "ssh", "start"], check=True)
2363
+ # Store connection info in a file for later reference
2364
+ connection_info = {
2365
+ "host": tunnel.tcp_socket[0],
2366
+ "port": tunnel.tcp_socket[1],
2367
+ "username": "root",
2368
+ "password": ssh_password,
2369
+ "app_name": app_name,
2370
+ "timestamp": datetime.datetime.now().isoformat()
2371
+ }
2372
+
2373
+ try:
2374
+ with open(os.path.expanduser("~/.modal_ssh_connection"), "w") as f:
2375
+ json.dump(connection_info, f, indent=2)
2376
+ print(f"šŸ“‹ Connection info saved to ~/.modal_ssh_connection")
2377
+ except Exception as e:
2378
+ print(f"āš ļø Could not save connection info: {e}")
2379
+
2380
+ # Keep the container and tunnel alive
2381
+ print("ā³ Container is running. Press Ctrl+C to stop...")
2382
+ try:
2383
+ while True:
2384
+ time.sleep(30)
2385
+ # Check if SSH service is still running
2386
+ try:
2387
+ subprocess.run(["service", "ssh", "status"], check=True,
2388
+ capture_output=True)
2389
+ except subprocess.CalledProcessError:
2390
+ print("āš ļø SSH service stopped, restarting...")
2391
+ subprocess.run(["service", "ssh", "start"], check=True)
2392
+ except KeyboardInterrupt:
2393
+ print("\nšŸ‘‹ Container stopping...")
2394
+ return
2481
2395
 
2482
2396
  # Run the container
2483
2397
  try:
2484
2398
  print("ā³ Starting container... This may take 1-2 minutes...")
2485
2399
 
2486
- # Start the container in a new thread to avoid blocking
2487
- with modal.enable_output():
2488
- with app.run():
2489
- ssh_container_function.remote()
2490
-
2400
+ # Run the container function
2401
+ with app.run():
2402
+ ssh_container.remote()
2403
+
2491
2404
  return {
2492
2405
  "app_name": app_name,
2493
2406
  "ssh_password": ssh_password,
2494
2407
  "volume_name": volume_name
2495
2408
  }
2409
+
2496
2410
  except Exception as e:
2497
2411
  print(f"āŒ Error running container: {e}")
2412
+ import traceback
2413
+ traceback.print_exc()
2498
2414
  return None
2499
2415
 
2500
2416
  def fetch_setup_commands_from_api(repo_url):