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
Binary file
|
@@ -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
|
2188
|
+
# Check Modal authentication
|
2190
2189
|
try:
|
2191
|
-
|
2192
|
-
print("
|
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"
|
2290
|
-
|
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
|
-
#
|
2343
|
-
|
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
|
-
|
2359
|
-
|
2360
|
-
|
2361
|
-
|
2362
|
-
|
2363
|
-
|
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
|
-
|
2391
|
-
|
2392
|
-
|
2393
|
-
|
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,
|
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
|
2411
|
-
"""Start SSH container with password authentication and
|
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
|
-
#
|
2456
|
-
|
2457
|
-
|
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: {
|
2463
|
-
print(f"š SSH 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 {
|
2360
|
+
print(f"ssh -p {tunnel.tcp_socket[1]} root@{tunnel.tcp_socket[0]}")
|
2469
2361
|
print("=" * 80)
|
2470
2362
|
|
2471
|
-
#
|
2472
|
-
|
2473
|
-
|
2474
|
-
|
2475
|
-
|
2476
|
-
|
2477
|
-
|
2478
|
-
|
2479
|
-
|
2480
|
-
|
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
|
-
#
|
2487
|
-
with
|
2488
|
-
|
2489
|
-
|
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):
|