gitarsenal-cli 1.2.1 ā 1.2.2
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 +1 -1
- package/python/modal_proxy_service.py +12 -0
- package/python/test_modalSandboxScript.py +191 -107
package/package.json
CHANGED
@@ -223,6 +223,18 @@ def root():
|
|
223
223
|
"""Root endpoint for basic connectivity testing"""
|
224
224
|
return jsonify({"status": "ok", "message": "Modal proxy service is running"})
|
225
225
|
|
226
|
+
@app.route('/api/modal-tokens', methods=['GET'])
|
227
|
+
def get_modal_tokens():
|
228
|
+
"""Get Modal tokens (protected by API key)"""
|
229
|
+
if not authenticate_request():
|
230
|
+
return jsonify({"error": "Unauthorized"}), 401
|
231
|
+
|
232
|
+
# Return the server's Modal token
|
233
|
+
return jsonify({
|
234
|
+
"token_id": MODAL_TOKEN,
|
235
|
+
"token_secret": MODAL_TOKEN # For compatibility, use the same token
|
236
|
+
})
|
237
|
+
|
226
238
|
@app.route('/api/create-api-key', methods=['POST'])
|
227
239
|
def create_api_key():
|
228
240
|
"""Create a new API key (protected by admin key)"""
|
@@ -2181,17 +2181,113 @@ 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
|
+
|
2184
2185
|
def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_commands=None,
|
2185
2186
|
volume_name=None, timeout_minutes=60, ssh_password=None):
|
2186
|
-
"""Create a Modal SSH container with GPU support and
|
2187
|
+
"""Create a Modal SSH container with GPU support and tunneling"""
|
2187
2188
|
|
2188
|
-
# Check Modal
|
2189
|
+
# Check if Modal is authenticated
|
2189
2190
|
try:
|
2190
|
-
|
2191
|
-
print("
|
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")
|
2192
2288
|
except Exception as e:
|
2193
|
-
print(f"
|
2194
|
-
|
2289
|
+
print(f"ā ļø Error checking Modal authentication: {e}")
|
2290
|
+
print("Continuing anyway, but Modal operations may fail")
|
2195
2291
|
|
2196
2292
|
# Generate a unique app name with timestamp to avoid conflicts
|
2197
2293
|
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
@@ -2230,86 +2326,99 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
2230
2326
|
print(f"ā ļø Could not setup volume '{volume_name}': {e}")
|
2231
2327
|
print("ā ļø Continuing without persistent volume")
|
2232
2328
|
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
|
2233
2341
|
|
2234
|
-
#
|
2235
|
-
|
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'}")
|
2236
2347
|
|
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
|
+
|
2237
2357
|
# Create SSH-enabled image
|
2238
|
-
|
2239
|
-
|
2240
|
-
|
2241
|
-
|
2242
|
-
|
2243
|
-
|
2244
|
-
|
2245
|
-
|
2246
|
-
|
2247
|
-
#
|
2248
|
-
|
2249
|
-
|
2250
|
-
|
2251
|
-
|
2252
|
-
|
2253
|
-
|
2254
|
-
|
2255
|
-
|
2256
|
-
|
2257
|
-
|
2258
|
-
|
2259
|
-
|
2260
|
-
|
2261
|
-
|
2262
|
-
|
2263
|
-
|
2264
|
-
|
2265
|
-
|
2266
|
-
|
2267
|
-
|
2268
|
-
|
2269
|
-
"echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
|
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
|
+
)
|
2270
2389
|
)
|
2271
|
-
|
2272
|
-
|
2390
|
+
print("ā
SSH image built successfully")
|
2391
|
+
except Exception as e:
|
2392
|
+
print(f"ā Error building SSH image: {e}")
|
2393
|
+
return None
|
2394
|
+
|
2273
2395
|
# Configure volumes if available
|
2274
2396
|
volumes_config = {}
|
2275
2397
|
if volume:
|
2276
2398
|
volumes_config[volume_mount_path] = volume
|
2277
|
-
|
2278
|
-
# Define the SSH container function
|
2399
|
+
|
2400
|
+
# Define the SSH container function
|
2279
2401
|
@app.function(
|
2280
2402
|
image=ssh_image,
|
2281
|
-
timeout=timeout_minutes * 60,
|
2403
|
+
timeout=timeout_minutes * 60, # Convert to seconds
|
2282
2404
|
gpu=gpu_spec['gpu'],
|
2283
2405
|
cpu=2,
|
2284
2406
|
memory=8192,
|
2407
|
+
serialized=True,
|
2285
2408
|
volumes=volumes_config if volumes_config else None,
|
2286
2409
|
)
|
2287
|
-
def
|
2288
|
-
"""Start SSH container with password authentication and
|
2410
|
+
def ssh_container_function():
|
2411
|
+
"""Start SSH container with password authentication and optional setup."""
|
2289
2412
|
import subprocess
|
2290
2413
|
import time
|
2291
2414
|
import os
|
2292
2415
|
|
2293
|
-
print("š§ Setting up SSH container...")
|
2294
|
-
|
2295
2416
|
# Set root password
|
2296
|
-
print(f"š Setting root password...")
|
2297
2417
|
subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
|
2298
2418
|
|
2299
2419
|
# Start SSH service
|
2300
|
-
print("š Starting SSH service...")
|
2301
2420
|
subprocess.run(["service", "ssh", "start"], check=True)
|
2302
2421
|
|
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
|
-
|
2313
2422
|
# Clone repository if provided
|
2314
2423
|
if repo_url:
|
2315
2424
|
repo_name_from_url = repo_name or repo_url.split('/')[-1].replace('.git', '')
|
@@ -2343,74 +2452,49 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
2343
2452
|
if e.stderr:
|
2344
2453
|
print(f"ā Error: {e.stderr}")
|
2345
2454
|
|
2346
|
-
#
|
2347
|
-
print("š Creating unencrypted SSH tunnel...")
|
2348
|
-
|
2349
|
-
# Use Modal's unencrypted tunnel for SSH protocol
|
2455
|
+
# Create SSH tunnel
|
2350
2456
|
with modal.forward(22, unencrypted=True) as tunnel:
|
2457
|
+
host, port = tunnel.tcp_socket
|
2458
|
+
|
2351
2459
|
print("\n" + "=" * 80)
|
2352
2460
|
print("š SSH CONTAINER IS READY!")
|
2353
2461
|
print("=" * 80)
|
2354
|
-
print(f"š SSH Host: {
|
2355
|
-
print(f"š SSH Port: {
|
2462
|
+
print(f"š SSH Host: {host}")
|
2463
|
+
print(f"š SSH Port: {port}")
|
2356
2464
|
print(f"š¤ Username: root")
|
2357
2465
|
print(f"š Password: {ssh_password}")
|
2358
2466
|
print()
|
2359
2467
|
print("š CONNECT USING THIS COMMAND:")
|
2360
|
-
print(f"ssh -p {
|
2468
|
+
print(f"ssh -p {port} root@{host}")
|
2361
2469
|
print("=" * 80)
|
2362
2470
|
|
2363
|
-
#
|
2364
|
-
|
2365
|
-
|
2366
|
-
|
2367
|
-
|
2368
|
-
|
2369
|
-
|
2370
|
-
|
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
|
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)
|
2395
2481
|
|
2396
2482
|
# Run the container
|
2397
2483
|
try:
|
2398
2484
|
print("ā³ Starting container... This may take 1-2 minutes...")
|
2399
2485
|
|
2400
|
-
#
|
2401
|
-
with
|
2402
|
-
|
2403
|
-
|
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
|
+
|
2404
2491
|
return {
|
2405
2492
|
"app_name": app_name,
|
2406
2493
|
"ssh_password": ssh_password,
|
2407
2494
|
"volume_name": volume_name
|
2408
2495
|
}
|
2409
|
-
|
2410
2496
|
except Exception as e:
|
2411
2497
|
print(f"ā Error running container: {e}")
|
2412
|
-
import traceback
|
2413
|
-
traceback.print_exc()
|
2414
2498
|
return None
|
2415
2499
|
|
2416
2500
|
def fetch_setup_commands_from_api(repo_url):
|