gitarsenal-cli 1.1.6 → 1.1.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.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -46,7 +46,7 @@ The Modal proxy service allows users to access Modal services (like GPU-accelera
46
46
 
47
47
  This will provide you with a public URL that you can share with users.
48
48
 
49
- Current ngrok URL: `https://e74889c63199.ngrok-free.app`
49
+ Current ngrok URL: `https://cf487e07fd45.ngrok-free.app`
50
50
 
51
51
  ## Client-Side Setup
52
52
 
@@ -56,7 +56,7 @@ The Modal proxy service allows users to access Modal services (like GPU-accelera
56
56
  ```
57
57
 
58
58
  You'll be prompted to enter:
59
- - The proxy URL (the default is now set to `https://e74889c63199.ngrok-free.app`)
59
+ - The proxy URL (the default is now set to `https://cf487e07fd45.ngrok-free.app`)
60
60
  - Your API key
61
61
 
62
62
  2. **Check proxy service status**:
@@ -74,7 +74,7 @@ def check_proxy_config():
74
74
  print("⚠️ Modal proxy not configured. Setting up with default values...")
75
75
 
76
76
  # Set default proxy URL to the ngrok URL
77
- default_url = "https://e74889c63199.ngrok-free.app"
77
+ default_url = "https://cf487e07fd45.ngrok-free.app"
78
78
 
79
79
  # Update configuration with default URL
80
80
  config["proxy_url"] = default_url
@@ -32,7 +32,7 @@ class GitArsenalProxyClient:
32
32
 
33
33
  # If still no URL, use default
34
34
  if not self.base_url:
35
- self.base_url = "https://e74889c63199.ngrok-free.app" # Default to ngrok URL
35
+ self.base_url = "https://cf487e07fd45.ngrok-free.app" # Default to new ngrok URL
36
36
 
37
37
  # Warn if no API key
38
38
  if not self.api_key:
@@ -82,30 +82,32 @@ def setup_modal_auth():
82
82
  try:
83
83
  # Set the token in the environment
84
84
  os.environ["MODAL_TOKEN_ID"] = MODAL_TOKEN
85
- # Also set the token directly in modal.config
86
- modal.config._auth_config.token_id = MODAL_TOKEN
87
85
 
88
- # Verify token is working by attempting a simple operation
86
+ # Try to set the token using Modal CLI
89
87
  try:
90
- # Try to list volumes as a simple API test
91
- test_vol = modal.Volume.list()
92
- logger.info("Modal token verified successfully - able to list volumes")
93
- except Exception as vol_err:
94
- logger.error(f"Modal token verification failed: {vol_err}")
95
- # Try setting token again with different approach
96
- try:
97
- import subprocess
98
- result = subprocess.run(
99
- ["modal", "token", "set", MODAL_TOKEN],
100
- capture_output=True, text=True, check=True
101
- )
88
+ import subprocess
89
+ result = subprocess.run(
90
+ ["modal", "token", "set", MODAL_TOKEN],
91
+ capture_output=True, text=True, check=False
92
+ )
93
+ if result.returncode == 0:
102
94
  logger.info("Modal token set via CLI command")
103
- return True
104
- except Exception as cli_err:
105
- logger.error(f"Failed to set token via CLI: {cli_err}")
106
- return False
95
+ else:
96
+ logger.warning(f"CLI token set returned: {result.stderr}")
97
+ except Exception as cli_err:
98
+ logger.warning(f"Failed to set token via CLI: {cli_err}")
107
99
 
108
- logger.info("Modal token set in environment and config")
100
+ # Verify token is working by attempting a simple operation
101
+ try:
102
+ # Try a simple Modal operation
103
+ import modal.cli.app_create
104
+ logger.info("Modal module imported successfully")
105
+ return True
106
+ except Exception as e:
107
+ logger.error(f"Error importing Modal module: {e}")
108
+ return False
109
+
110
+ logger.info("Modal token set in environment")
109
111
  return True
110
112
  except Exception as e:
111
113
  logger.error(f"Error setting up Modal authentication: {e}")
@@ -201,10 +203,30 @@ def create_ssh_container():
201
203
  if not authenticate_request():
202
204
  return jsonify({"error": "Unauthorized"}), 401
203
205
 
204
- if not setup_modal_auth():
205
- return jsonify({"error": "Failed to set up Modal authentication"}), 500
206
+ # Check if Modal token is available
207
+ if not MODAL_TOKEN:
208
+ logger.error("Cannot create SSH container: No Modal token available")
209
+ return jsonify({"error": "Modal token not configured on server"}), 500
206
210
 
207
211
  try:
212
+ # Set the token directly in environment
213
+ os.environ["MODAL_TOKEN_ID"] = MODAL_TOKEN
214
+ logger.info(f"Set MODAL_TOKEN_ID in environment (length: {len(MODAL_TOKEN)})")
215
+
216
+ # Try to set token via CLI as well
217
+ try:
218
+ import subprocess
219
+ result = subprocess.run(
220
+ ["modal", "token", "set", MODAL_TOKEN],
221
+ capture_output=True, text=True
222
+ )
223
+ if result.returncode == 0:
224
+ logger.info("Successfully set token via Modal CLI")
225
+ else:
226
+ logger.warning(f"Modal CLI token set returned: {result.stderr}")
227
+ except Exception as e:
228
+ logger.warning(f"Failed to set token via CLI: {e}")
229
+
208
230
  data = request.json
209
231
  gpu_type = data.get('gpu_type', 'A10G')
210
232
  repo_url = data.get('repo_url')
@@ -229,39 +251,12 @@ def create_ssh_container():
229
251
  logger.info(f"Modal token status before thread: {token_status}")
230
252
  logger.info(f"Modal token length: {len(MODAL_TOKEN) if MODAL_TOKEN else 'N/A'}")
231
253
 
232
- # Ensure Modal token is set in environment
233
- if MODAL_TOKEN:
234
- os.environ["MODAL_TOKEN_ID"] = MODAL_TOKEN
235
- # Also set directly in modal.config
236
- modal.config._auth_config.token_id = MODAL_TOKEN
237
- logger.info("Modal token explicitly set in environment and config")
238
-
239
254
  # Start container creation in a separate thread
240
255
  def create_container_thread():
241
256
  try:
242
- # Ensure Modal token is set before creating container
243
- if not setup_modal_auth():
244
- logger.error("Failed to set up Modal authentication in thread")
245
- return
246
-
247
- # Explicitly set the Modal token in the environment again for this thread
257
+ # Set token in environment for this thread
248
258
  os.environ["MODAL_TOKEN_ID"] = MODAL_TOKEN
249
-
250
- # Directly set the token in Modal config
251
- modal.config._auth_config.token_id = MODAL_TOKEN
252
-
253
- # Log token status for debugging
254
- token_status = "Token is set" if MODAL_TOKEN else "Token is missing"
255
- logger.info(f"Modal token status in thread: {token_status}")
256
- logger.info(f"Modal token length in thread: {len(MODAL_TOKEN) if MODAL_TOKEN else 'N/A'}")
257
-
258
- # Verify token is actually working
259
- try:
260
- # Try a simple Modal operation to verify authentication
261
- import modal.cli.app_create
262
- logger.info("Modal module imported successfully in thread")
263
- except Exception as auth_err:
264
- logger.error(f"Modal authentication verification error: {auth_err}")
259
+ logger.info(f"Set MODAL_TOKEN_ID in thread (length: {len(MODAL_TOKEN)})")
265
260
 
266
261
  result = create_modal_ssh_container(
267
262
  gpu_type,
@@ -2075,47 +2075,44 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2075
2075
  modal_token = os.environ.get("MODAL_TOKEN_ID")
2076
2076
  if not modal_token:
2077
2077
  print("⚠️ MODAL_TOKEN_ID not found in environment.")
2078
- # Try to set from modal.config if available
2078
+ # Try to get from MODAL_TOKEN
2079
+ modal_token = os.environ.get("MODAL_TOKEN")
2080
+ if modal_token:
2081
+ print("✅ Found token in MODAL_TOKEN environment variable")
2082
+ os.environ["MODAL_TOKEN_ID"] = modal_token
2083
+
2084
+ if modal_token:
2085
+ print(f"✅ Modal token found (length: {len(modal_token)})")
2086
+
2087
+ # Use the Modal CLI to set the token
2079
2088
  try:
2080
- modal_token = modal.config._auth_config.token_id
2081
- if modal_token:
2082
- print(" Found token in modal.config")
2083
- # Set it in environment for consistency
2084
- os.environ["MODAL_TOKEN_ID"] = modal_token
2089
+ import subprocess
2090
+ result = subprocess.run(
2091
+ ["modal", "token", "set", modal_token],
2092
+ capture_output=True, text=True
2093
+ )
2094
+ if result.returncode == 0:
2095
+ print("✅ Modal token set via CLI")
2096
+ else:
2097
+ print(f"⚠️ Modal CLI token set returned: {result.stderr}")
2085
2098
  except Exception as e:
2086
- print(f"⚠️ Error accessing modal.config: {e}")
2087
-
2088
- # If still no token, try to get workspace name as authentication test
2089
- if not modal_token:
2090
- # This will raise an exception if not authenticated
2091
- workspace = modal.config.get_current_workspace_name()
2092
- print(f"✅ Modal authentication verified (workspace: {workspace})")
2099
+ print(f"⚠️ Error setting token via CLI: {e}")
2093
2100
  else:
2094
- print(f" Modal token found (length: {len(modal_token)})")
2095
- # Explicitly set the token in modal config
2096
- modal.config._auth_config.token_id = modal_token
2097
- print("✅ Token explicitly set in modal.config")
2098
- except modal.exception.AuthError:
2099
- print("\n" + "="*80)
2100
- print("🔑 MODAL AUTHENTICATION REQUIRED")
2101
- print("="*80)
2102
- print("GitArsenal requires Modal authentication to create cloud environments.")
2103
-
2104
- # Check if token is in environment
2105
- modal_token = os.environ.get("MODAL_TOKEN_ID")
2101
+ print(" No Modal token found in environment variables")
2102
+ return None
2103
+
2104
+ except Exception as e:
2105
+ print(f"⚠️ Error checking Modal token: {e}")
2106
+ # Try to use the token from environment
2107
+ modal_token = os.environ.get("MODAL_TOKEN_ID") or os.environ.get("MODAL_TOKEN")
2106
2108
  if not modal_token:
2107
- print("⚠️ No Modal token found in environment.")
2109
+ print(" No Modal token available. Cannot proceed.")
2108
2110
  return None
2109
- else:
2110
- print("🔄 Using Modal token from environment")
2111
- # Try to authenticate with the token
2112
- try:
2113
- # Set token directly in modal config
2114
- modal.config._auth_config.token_id = modal_token
2115
- print("✅ Modal token set in config")
2116
- except Exception as e:
2117
- print(f"⚠️ Error setting Modal token: {e}")
2118
- return None
2111
+
2112
+ print(f"🔄 Using Modal token from environment (length: {len(modal_token)})")
2113
+ # Set it in both environment variables
2114
+ os.environ["MODAL_TOKEN_ID"] = modal_token
2115
+ os.environ["MODAL_TOKEN"] = modal_token
2119
2116
  except Exception as e:
2120
2117
  print(f"⚠️ Error checking Modal authentication: {e}")
2121
2118
  print("Continuing anyway, but Modal operations may fail")
@@ -2176,83 +2173,59 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2176
2173
  print(f" - MODAL_TOKEN_ID in env: {'Yes' if modal_token else 'No'}")
2177
2174
  print(f" - Token length: {len(modal_token) if modal_token else 'N/A'}")
2178
2175
 
2176
+ # Verify we can create a Modal app
2179
2177
  try:
2180
- workspace = modal.config.get_current_workspace_name()
2181
- print(f" - Current workspace: {workspace}")
2182
- except Exception as e:
2183
- print(f" - Error getting workspace: {e}")
2184
-
2185
- # Verify token is valid by trying a simple Modal operation
2186
- try:
2187
- print("🔍 Verifying Modal token validity...")
2188
- # Try to access Modal API
2189
- if not modal_token:
2190
- print("❌ No Modal token available. Cannot proceed.")
2191
- return None
2192
-
2193
- # Set token directly in modal config again to be sure
2194
- modal.config._auth_config.token_id = modal_token
2195
-
2196
- # Try to list volumes as a simple API test
2197
- try:
2198
- test_vol = modal.Volume.list()
2199
- print(f"✅ Modal token is valid! Successfully listed volumes.")
2200
- except Exception as vol_err:
2201
- print(f"❌ Token validation failed: {vol_err}")
2202
- print("Please check that your Modal token is valid and properly set.")
2203
- return None
2178
+ print("🔍 Testing Modal app creation...")
2179
+ app = modal.App(app_name)
2180
+ print("✅ Created Modal app successfully")
2204
2181
  except Exception as e:
2205
- print(f"❌ Error during token validation: {e}")
2182
+ print(f"❌ Error creating Modal app: {e}")
2206
2183
  return None
2207
2184
 
2208
2185
  # Create SSH-enabled image
2209
- ssh_image = (
2210
- modal.Image.debian_slim()
2211
- .apt_install(
2212
- "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
2213
- "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
2214
- "gpg", "ca-certificates", "software-properties-common"
2215
- )
2216
- .pip_install("uv", "modal") # Fast Python package installer and Modal
2217
- .run_commands(
2218
- # Create SSH directory
2219
- "mkdir -p /var/run/sshd",
2220
- "mkdir -p /root/.ssh",
2221
- "chmod 700 /root/.ssh",
2222
-
2223
- # Configure SSH server
2224
- "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
2225
- "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
2226
- "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
2227
-
2228
- # SSH keep-alive settings
2229
- "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
2230
- "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
2231
-
2232
- # Generate SSH host keys
2233
- "ssh-keygen -A",
2234
-
2235
- # Install Modal CLI
2236
- "pip install modal",
2237
-
2238
- # Set up a nice bash prompt
2239
- "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
2240
- )
2241
- )
2242
-
2243
- # Create the Modal app
2244
2186
  try:
2245
- app = modal.App(app_name)
2246
- print("✅ Created Modal app successfully")
2187
+ print("📦 Building SSH-enabled image...")
2188
+ ssh_image = (
2189
+ modal.Image.debian_slim()
2190
+ .apt_install(
2191
+ "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
2192
+ "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
2193
+ "gpg", "ca-certificates", "software-properties-common"
2194
+ )
2195
+ .pip_install("uv", "modal") # Fast Python package installer and Modal
2196
+ .run_commands(
2197
+ # Create SSH directory
2198
+ "mkdir -p /var/run/sshd",
2199
+ "mkdir -p /root/.ssh",
2200
+ "chmod 700 /root/.ssh",
2201
+
2202
+ # Configure SSH server
2203
+ "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
2204
+ "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
2205
+ "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
2206
+
2207
+ # SSH keep-alive settings
2208
+ "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
2209
+ "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
2210
+
2211
+ # Generate SSH host keys
2212
+ "ssh-keygen -A",
2213
+
2214
+ # Set up a nice bash prompt
2215
+ "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
2216
+ )
2217
+ )
2218
+ print("✅ SSH image built successfully")
2247
2219
  except Exception as e:
2248
- print(f"❌ Error creating Modal app: {e}")
2220
+ print(f"❌ Error building SSH image: {e}")
2249
2221
  return None
2250
-
2222
+
2251
2223
  # Configure volumes if available
2252
2224
  volumes_config = {}
2253
2225
  if volume:
2254
2226
  volumes_config[volume_mount_path] = volume
2255
-
2227
+
2228
+ # Define the SSH container function
2256
2229
  @app.function(
2257
2230
  image=ssh_image,
2258
2231
  timeout=timeout_minutes * 60, # Convert to seconds
@@ -2262,27 +2235,17 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2262
2235
  serialized=True,
2263
2236
  volumes=volumes_config if volumes_config else None,
2264
2237
  )
2265
- def run_ssh_container():
2238
+ def ssh_container_function():
2266
2239
  """Start SSH container with password authentication and optional setup."""
2267
2240
  import subprocess
2268
2241
  import time
2269
2242
  import os
2270
2243
 
2271
2244
  # Set root password
2272
- print("🔐 Setting up authentication...")
2273
- subprocess.run(
2274
- ["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"],
2275
- check=True
2276
- )
2277
-
2278
- print("✅ Root password configured")
2279
-
2280
- # Start SSH daemon in background
2281
- print("🔄 Starting SSH daemon...")
2282
- ssh_process = subprocess.Popen(["/usr/sbin/sshd", "-D"])
2245
+ subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
2283
2246
 
2284
- # Give SSH daemon a moment to start
2285
- time.sleep(2)
2247
+ # Start SSH service
2248
+ subprocess.run(["service", "ssh", "start"], check=True)
2286
2249
 
2287
2250
  # Clone repository if provided
2288
2251
  if repo_url:
@@ -2290,18 +2253,14 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2290
2253
  print(f"📥 Cloning repository: {repo_url}")
2291
2254
 
2292
2255
  try:
2293
- subprocess.run(["git", "clone", repo_url, "/root/repo"], check=True)
2256
+ subprocess.run(["git", "clone", repo_url], check=True, cwd="/root")
2294
2257
  print(f"✅ Repository cloned successfully: {repo_name_from_url}")
2295
2258
 
2296
2259
  # Change to repository directory
2297
- repo_dir = f"/root/repo"
2298
- os.chdir(repo_dir)
2299
- print(f"📂 Changed to repository directory: {repo_dir}")
2300
-
2301
- # Initialize git submodules if they exist
2302
- if os.path.exists(".gitmodules"):
2303
- print("📦 Initializing git submodules...")
2304
- subprocess.run(["git", "submodule", "update", "--init", "--recursive"], check=True)
2260
+ repo_dir = f"/root/{repo_name_from_url}"
2261
+ if os.path.exists(repo_dir):
2262
+ os.chdir(repo_dir)
2263
+ print(f"📂 Changed to repository directory: {repo_dir}")
2305
2264
 
2306
2265
  except subprocess.CalledProcessError as e:
2307
2266
  print(f"❌ Failed to clone repository: {e}")
@@ -2312,16 +2271,8 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2312
2271
  for i, cmd in enumerate(setup_commands, 1):
2313
2272
  print(f"📋 Executing command {i}/{len(setup_commands)}: {cmd}")
2314
2273
  try:
2315
- # Run in repository directory if it exists, otherwise root
2316
- work_dir = "/root/repo" if os.path.exists("/root/repo") else "/root"
2317
- result = subprocess.run(
2318
- cmd,
2319
- shell=True,
2320
- check=True,
2321
- cwd=work_dir,
2322
- capture_output=True,
2323
- text=True
2324
- )
2274
+ result = subprocess.run(cmd, shell=True, check=True,
2275
+ capture_output=True, text=True)
2325
2276
  if result.stdout:
2326
2277
  print(f"✅ Output: {result.stdout}")
2327
2278
  except subprocess.CalledProcessError as e:
@@ -2329,11 +2280,9 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2329
2280
  if e.stderr:
2330
2281
  print(f"❌ Error: {e.stderr}")
2331
2282
 
2332
- # Create unencrypted tunnel for SSH (port 22)
2333
- print("🌐 Creating SSH tunnel...")
2334
- with modal.forward(22, unencrypted=True) as tunnel:
2335
- # Print connection information
2336
- host, port = tunnel.tcp_socket
2283
+ # Create SSH tunnel
2284
+ with modal.forward(22) as tunnel:
2285
+ host, port = tunnel.host, tunnel.port
2337
2286
 
2338
2287
  print("\n" + "=" * 80)
2339
2288
  print("🎉 SSH CONTAINER IS READY!")
@@ -2345,80 +2294,33 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2345
2294
  print()
2346
2295
  print("🔗 CONNECT USING THIS COMMAND:")
2347
2296
  print(f"ssh -p {port} root@{host}")
2348
- print()
2349
- print("💡 Connection Tips:")
2350
- print("• Copy the password above and paste when prompted")
2351
- print("• Use Ctrl+C to disconnect (container will keep running)")
2352
- print("• Type 'exit' in the SSH session to close the connection")
2353
- if repo_url:
2354
- print("• Your repository is in: /root/repo")
2355
- if volume:
2356
- print(f"• Persistent storage is mounted at: {volume_mount_path}")
2357
- print(f"• Container will auto-terminate in {timeout_minutes} minutes")
2358
- print()
2359
- print("🛠️ Available Tools:")
2360
- print("• Python 3 with pip and uv package manager")
2361
- print("• Git, curl, wget, vim, nano, htop")
2362
- print("• tmux and screen for session management")
2363
- print(f"• {gpu_type} GPU access")
2364
2297
  print("=" * 80)
2365
2298
 
2366
- # Keep the container running and monitor SSH daemon
2367
- try:
2368
- start_time = time.time()
2369
- last_check = start_time
2370
-
2371
- while True:
2372
- current_time = time.time()
2373
-
2374
- # Check SSH daemon every 30 seconds
2375
- if current_time - last_check >= 30:
2376
- if ssh_process.poll() is not None:
2377
- print("⚠️ SSH daemon stopped unexpectedly, restarting...")
2378
- ssh_process = subprocess.Popen(["/usr/sbin/sshd", "-D"])
2379
- time.sleep(2)
2380
-
2381
- # Print alive status every 5 minutes
2382
- elapsed_minutes = (current_time - start_time) / 60
2383
- if int(elapsed_minutes) % 5 == 0 and elapsed_minutes > 0:
2384
- remaining_minutes = timeout_minutes - elapsed_minutes
2385
- print(f"⏱️ Container alive for {elapsed_minutes:.0f} minutes, {remaining_minutes:.0f} minutes remaining")
2386
-
2387
- last_check = current_time
2388
-
2389
- time.sleep(10)
2390
-
2391
- except KeyboardInterrupt:
2392
- print("\n👋 Received shutdown signal...")
2393
- print("🔄 Stopping SSH daemon...")
2394
- ssh_process.terminate()
2395
- ssh_process.wait()
2396
- print("✅ Container shutting down gracefully")
2397
- return
2299
+ # Keep the container running
2300
+ while True:
2301
+ time.sleep(30)
2302
+ # Check if SSH service is still running
2303
+ try:
2304
+ subprocess.run(["service", "ssh", "status"], check=True,
2305
+ capture_output=True)
2306
+ except subprocess.CalledProcessError:
2307
+ print("⚠️ SSH service stopped, restarting...")
2308
+ subprocess.run(["service", "ssh", "start"], check=True)
2398
2309
 
2399
2310
  # Run the container
2400
- print("⏳ Starting container... This may take 1-2 minutes...")
2401
- print("📦 Building image and allocating resources...")
2402
-
2403
2311
  try:
2404
- # Start the container and run the function
2312
+ print("⏳ Starting container... This may take 1-2 minutes...")
2313
+
2314
+ # Start the container in a new thread to avoid blocking
2405
2315
  with modal.enable_output():
2406
2316
  with app.run():
2407
- run_ssh_container.remote()
2317
+ ssh_container_function.remote()
2408
2318
 
2409
2319
  return {
2410
2320
  "app_name": app_name,
2411
2321
  "ssh_password": ssh_password,
2412
- "volume_name": volume_name,
2413
- "volume_mount_path": volume_mount_path if volume else None
2322
+ "volume_name": volume_name
2414
2323
  }
2415
- except modal.exception.AuthError as auth_err:
2416
- print(f"❌ Modal authentication error: {auth_err}")
2417
- print("🔑 Please check that your Modal token is valid and properly set")
2418
- return None
2419
- except KeyboardInterrupt:
2420
- print("\n👋 Container startup interrupted")
2421
- return None
2422
2324
  except Exception as e:
2423
2325
  print(f"❌ Error running container: {e}")
2424
2326
  return None