gitarsenal-cli 1.1.7 → 1.1.9

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.7",
3
+ "version": "1.1.9",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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,31 @@ 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
+ os.environ["MODAL_TOKEN"] = MODAL_TOKEN
215
+ logger.info(f"Set MODAL_TOKEN_ID in environment (length: {len(MODAL_TOKEN)})")
216
+
217
+ # Try to set token via CLI as well
218
+ try:
219
+ import subprocess
220
+ result = subprocess.run(
221
+ ["modal", "token", "set", MODAL_TOKEN],
222
+ capture_output=True, text=True
223
+ )
224
+ if result.returncode == 0:
225
+ logger.info("Successfully set token via Modal CLI")
226
+ else:
227
+ logger.warning(f"Modal CLI token set returned: {result.stderr}")
228
+ except Exception as e:
229
+ logger.warning(f"Failed to set token via CLI: {e}")
230
+
208
231
  data = request.json
209
232
  gpu_type = data.get('gpu_type', 'A10G')
210
233
  repo_url = data.get('repo_url')
@@ -229,39 +252,33 @@ def create_ssh_container():
229
252
  logger.info(f"Modal token status before thread: {token_status}")
230
253
  logger.info(f"Modal token length: {len(MODAL_TOKEN) if MODAL_TOKEN else 'N/A'}")
231
254
 
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
255
  # Start container creation in a separate thread
240
256
  def create_container_thread():
241
257
  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
258
+ # Set token in environment for this thread
248
259
  os.environ["MODAL_TOKEN_ID"] = MODAL_TOKEN
260
+ os.environ["MODAL_TOKEN"] = MODAL_TOKEN
261
+ logger.info(f"Set MODAL_TOKEN_ID in thread (length: {len(MODAL_TOKEN)})")
249
262
 
250
- # Directly set the token in Modal config
251
- modal.config._auth_config.token_id = MODAL_TOKEN
263
+ # Create a copy of the environment variables for the container creation
264
+ env_copy = os.environ.copy()
265
+ env_copy["MODAL_TOKEN_ID"] = MODAL_TOKEN
266
+ env_copy["MODAL_TOKEN"] = MODAL_TOKEN
252
267
 
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
268
+ # Try to set token via CLI again in this thread
259
269
  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}")
270
+ import subprocess
271
+ subprocess.run(
272
+ ["modal", "token", "set", MODAL_TOKEN],
273
+ capture_output=True, text=True, check=False
274
+ )
275
+ logger.info("Set Modal token via CLI in thread")
276
+ except Exception as e:
277
+ logger.warning(f"Failed to set token via CLI in thread: {e}")
278
+
279
+ # Explicitly print token status for debugging
280
+ logger.info(f"MODAL_TOKEN_ID in thread env: {os.environ.get('MODAL_TOKEN_ID')}")
281
+ logger.info(f"MODAL_TOKEN in thread env: {os.environ.get('MODAL_TOKEN')}")
265
282
 
266
283
  result = create_modal_ssh_container(
267
284
  gpu_type,
@@ -2069,53 +2069,95 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2069
2069
 
2070
2070
  # Check if Modal is authenticated
2071
2071
  try:
2072
+ # Print all environment variables for debugging
2073
+ print("šŸ” DEBUG: Checking environment variables")
2074
+ modal_token_id = os.environ.get("MODAL_TOKEN_ID")
2075
+ modal_token = os.environ.get("MODAL_TOKEN")
2076
+ print(f"šŸ” MODAL_TOKEN_ID exists: {'Yes' if modal_token_id else 'No'}")
2077
+ print(f"šŸ” MODAL_TOKEN exists: {'Yes' if modal_token else 'No'}")
2078
+ if modal_token_id:
2079
+ print(f"šŸ” MODAL_TOKEN_ID length: {len(modal_token_id)}")
2080
+ if modal_token:
2081
+ print(f"šŸ” MODAL_TOKEN length: {len(modal_token)}")
2082
+
2072
2083
  # Try to access Modal token to check authentication
2073
2084
  try:
2074
2085
  # Check if token is set in environment
2075
- modal_token = os.environ.get("MODAL_TOKEN_ID")
2076
- if not modal_token:
2086
+ modal_token_id = os.environ.get("MODAL_TOKEN_ID")
2087
+ if not modal_token_id:
2077
2088
  print("āš ļø MODAL_TOKEN_ID not found in environment.")
2078
- # Try to set from modal.config if available
2089
+ # Try to get from MODAL_TOKEN
2090
+ modal_token = os.environ.get("MODAL_TOKEN")
2091
+ if modal_token:
2092
+ print("āœ… Found token in MODAL_TOKEN environment variable")
2093
+ os.environ["MODAL_TOKEN_ID"] = modal_token
2094
+ modal_token_id = modal_token
2095
+ print(f"āœ… Set MODAL_TOKEN_ID from MODAL_TOKEN (length: {len(modal_token)})")
2096
+
2097
+ if modal_token_id:
2098
+ print(f"āœ… Modal token found (length: {len(modal_token_id)})")
2099
+
2100
+ # Use the Modal CLI to set the token
2079
2101
  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
2102
+ import subprocess
2103
+ print(f"šŸ”„ Setting token via Modal CLI (token length: {len(modal_token_id)})")
2104
+ result = subprocess.run(
2105
+ ["modal", "token", "set", modal_token_id],
2106
+ capture_output=True, text=True
2107
+ )
2108
+ if result.returncode == 0:
2109
+ print("āœ… Modal token set via CLI")
2110
+ else:
2111
+ print(f"āš ļø Modal CLI token set returned: {result.stderr}")
2085
2112
  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})")
2093
- 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")
2106
- if not modal_token:
2107
- print("āš ļø No Modal token found in environment.")
2108
- return None
2113
+ print(f"āš ļø Error setting token via CLI: {e}")
2109
2114
  else:
2110
- print("šŸ”„ Using Modal token from environment")
2111
- # Try to authenticate with the token
2115
+ print("āŒ No Modal token found in environment variables")
2116
+ # Try to get from file as a last resort
2112
2117
  try:
2113
- # Set token directly in modal config
2114
- modal.config._auth_config.token_id = modal_token
2115
- print("āœ… Modal token set in config")
2118
+ home_dir = os.path.expanduser("~")
2119
+ modal_dir = os.path.join(home_dir, ".modal")
2120
+ token_file = os.path.join(modal_dir, "token.json")
2121
+ if os.path.exists(token_file):
2122
+ print(f"šŸ” Found Modal token file at {token_file}")
2123
+ with open(token_file, 'r') as f:
2124
+ import json
2125
+ token_data = json.load(f)
2126
+ if "token_id" in token_data:
2127
+ modal_token_id = token_data["token_id"]
2128
+ os.environ["MODAL_TOKEN_ID"] = modal_token_id
2129
+ os.environ["MODAL_TOKEN"] = modal_token_id
2130
+ print(f"āœ… Loaded token from file (length: {len(modal_token_id)})")
2131
+ else:
2132
+ print("āŒ Token file does not contain token_id")
2133
+ else:
2134
+ print("āŒ Modal token file not found")
2116
2135
  except Exception as e:
2117
- print(f"āš ļø Error setting Modal token: {e}")
2136
+ print(f"āŒ Error loading token from file: {e}")
2137
+
2138
+ if not os.environ.get("MODAL_TOKEN_ID"):
2139
+ print("āŒ Could not find Modal token in any location")
2118
2140
  return None
2141
+
2142
+ except Exception as e:
2143
+ print(f"āš ļø Error checking Modal token: {e}")
2144
+ # Try to use the token from environment
2145
+ modal_token_id = os.environ.get("MODAL_TOKEN_ID")
2146
+ modal_token = os.environ.get("MODAL_TOKEN")
2147
+ if modal_token_id:
2148
+ print(f"šŸ”„ Using MODAL_TOKEN_ID from environment (length: {len(modal_token_id)})")
2149
+ elif modal_token:
2150
+ print(f"šŸ”„ Using MODAL_TOKEN from environment (length: {len(modal_token)})")
2151
+ os.environ["MODAL_TOKEN_ID"] = modal_token
2152
+ modal_token_id = modal_token
2153
+ else:
2154
+ print("āŒ No Modal token available. Cannot proceed.")
2155
+ return None
2156
+
2157
+ # Set it in both environment variables
2158
+ os.environ["MODAL_TOKEN_ID"] = modal_token_id
2159
+ os.environ["MODAL_TOKEN"] = modal_token_id
2160
+ print("āœ… Set both MODAL_TOKEN_ID and MODAL_TOKEN environment variables")
2119
2161
  except Exception as e:
2120
2162
  print(f"āš ļø Error checking Modal authentication: {e}")
2121
2163
  print("Continuing anyway, but Modal operations may fail")
@@ -2176,83 +2218,59 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2176
2218
  print(f" - MODAL_TOKEN_ID in env: {'Yes' if modal_token else 'No'}")
2177
2219
  print(f" - Token length: {len(modal_token) if modal_token else 'N/A'}")
2178
2220
 
2221
+ # Verify we can create a Modal app
2179
2222
  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
2223
+ print("šŸ” Testing Modal app creation...")
2224
+ app = modal.App(app_name)
2225
+ print("āœ… Created Modal app successfully")
2204
2226
  except Exception as e:
2205
- print(f"āŒ Error during token validation: {e}")
2227
+ print(f"āŒ Error creating Modal app: {e}")
2206
2228
  return None
2207
2229
 
2208
2230
  # 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
2231
  try:
2245
- app = modal.App(app_name)
2246
- print("āœ… Created Modal app successfully")
2232
+ print("šŸ“¦ Building SSH-enabled image...")
2233
+ ssh_image = (
2234
+ modal.Image.debian_slim()
2235
+ .apt_install(
2236
+ "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
2237
+ "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
2238
+ "gpg", "ca-certificates", "software-properties-common"
2239
+ )
2240
+ .pip_install("uv", "modal") # Fast Python package installer and Modal
2241
+ .run_commands(
2242
+ # Create SSH directory
2243
+ "mkdir -p /var/run/sshd",
2244
+ "mkdir -p /root/.ssh",
2245
+ "chmod 700 /root/.ssh",
2246
+
2247
+ # Configure SSH server
2248
+ "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
2249
+ "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
2250
+ "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
2251
+
2252
+ # SSH keep-alive settings
2253
+ "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
2254
+ "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
2255
+
2256
+ # Generate SSH host keys
2257
+ "ssh-keygen -A",
2258
+
2259
+ # Set up a nice bash prompt
2260
+ "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
2261
+ )
2262
+ )
2263
+ print("āœ… SSH image built successfully")
2247
2264
  except Exception as e:
2248
- print(f"āŒ Error creating Modal app: {e}")
2265
+ print(f"āŒ Error building SSH image: {e}")
2249
2266
  return None
2250
-
2267
+
2251
2268
  # Configure volumes if available
2252
2269
  volumes_config = {}
2253
2270
  if volume:
2254
2271
  volumes_config[volume_mount_path] = volume
2255
-
2272
+
2273
+ # Define the SSH container function
2256
2274
  @app.function(
2257
2275
  image=ssh_image,
2258
2276
  timeout=timeout_minutes * 60, # Convert to seconds
@@ -2262,27 +2280,17 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2262
2280
  serialized=True,
2263
2281
  volumes=volumes_config if volumes_config else None,
2264
2282
  )
2265
- def run_ssh_container():
2283
+ def ssh_container_function():
2266
2284
  """Start SSH container with password authentication and optional setup."""
2267
2285
  import subprocess
2268
2286
  import time
2269
2287
  import os
2270
2288
 
2271
2289
  # 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"])
2290
+ subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
2283
2291
 
2284
- # Give SSH daemon a moment to start
2285
- time.sleep(2)
2292
+ # Start SSH service
2293
+ subprocess.run(["service", "ssh", "start"], check=True)
2286
2294
 
2287
2295
  # Clone repository if provided
2288
2296
  if repo_url:
@@ -2290,18 +2298,14 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2290
2298
  print(f"šŸ“„ Cloning repository: {repo_url}")
2291
2299
 
2292
2300
  try:
2293
- subprocess.run(["git", "clone", repo_url, "/root/repo"], check=True)
2301
+ subprocess.run(["git", "clone", repo_url], check=True, cwd="/root")
2294
2302
  print(f"āœ… Repository cloned successfully: {repo_name_from_url}")
2295
2303
 
2296
2304
  # 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)
2305
+ repo_dir = f"/root/{repo_name_from_url}"
2306
+ if os.path.exists(repo_dir):
2307
+ os.chdir(repo_dir)
2308
+ print(f"šŸ“‚ Changed to repository directory: {repo_dir}")
2305
2309
 
2306
2310
  except subprocess.CalledProcessError as e:
2307
2311
  print(f"āŒ Failed to clone repository: {e}")
@@ -2312,16 +2316,8 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2312
2316
  for i, cmd in enumerate(setup_commands, 1):
2313
2317
  print(f"šŸ“‹ Executing command {i}/{len(setup_commands)}: {cmd}")
2314
2318
  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
- )
2319
+ result = subprocess.run(cmd, shell=True, check=True,
2320
+ capture_output=True, text=True)
2325
2321
  if result.stdout:
2326
2322
  print(f"āœ… Output: {result.stdout}")
2327
2323
  except subprocess.CalledProcessError as e:
@@ -2329,11 +2325,9 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2329
2325
  if e.stderr:
2330
2326
  print(f"āŒ Error: {e.stderr}")
2331
2327
 
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
2328
+ # Create SSH tunnel
2329
+ with modal.forward(22) as tunnel:
2330
+ host, port = tunnel.host, tunnel.port
2337
2331
 
2338
2332
  print("\n" + "=" * 80)
2339
2333
  print("šŸŽ‰ SSH CONTAINER IS READY!")
@@ -2345,80 +2339,33 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2345
2339
  print()
2346
2340
  print("šŸ”— CONNECT USING THIS COMMAND:")
2347
2341
  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
2342
  print("=" * 80)
2365
2343
 
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
2344
+ # Keep the container running
2345
+ while True:
2346
+ time.sleep(30)
2347
+ # Check if SSH service is still running
2348
+ try:
2349
+ subprocess.run(["service", "ssh", "status"], check=True,
2350
+ capture_output=True)
2351
+ except subprocess.CalledProcessError:
2352
+ print("āš ļø SSH service stopped, restarting...")
2353
+ subprocess.run(["service", "ssh", "start"], check=True)
2398
2354
 
2399
2355
  # Run the container
2400
- print("ā³ Starting container... This may take 1-2 minutes...")
2401
- print("šŸ“¦ Building image and allocating resources...")
2402
-
2403
2356
  try:
2404
- # Start the container and run the function
2357
+ print("ā³ Starting container... This may take 1-2 minutes...")
2358
+
2359
+ # Start the container in a new thread to avoid blocking
2405
2360
  with modal.enable_output():
2406
2361
  with app.run():
2407
- run_ssh_container.remote()
2362
+ ssh_container_function.remote()
2408
2363
 
2409
2364
  return {
2410
2365
  "app_name": app_name,
2411
2366
  "ssh_password": ssh_password,
2412
- "volume_name": volume_name,
2413
- "volume_mount_path": volume_mount_path if volume else None
2367
+ "volume_name": volume_name
2414
2368
  }
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
2369
  except Exception as e:
2423
2370
  print(f"āŒ Error running container: {e}")
2424
2371
  return None