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
Binary file
|
Binary file
|
@@ -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
|
-
#
|
86
|
+
# Try to set the token using Modal CLI
|
89
87
|
try:
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
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
|
205
|
-
|
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
|
-
#
|
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
|
-
#
|
251
|
-
|
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
|
-
#
|
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
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
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
|
-
|
2076
|
-
if not
|
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
|
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
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
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
|
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("
|
2111
|
-
# Try to
|
2115
|
+
print("ā No Modal token found in environment variables")
|
2116
|
+
# Try to get from file as a last resort
|
2112
2117
|
try:
|
2113
|
-
|
2114
|
-
|
2115
|
-
|
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"
|
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
|
-
|
2181
|
-
|
2182
|
-
|
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
|
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
|
-
|
2246
|
-
|
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
|
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
|
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
|
-
|
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
|
-
#
|
2285
|
-
|
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
|
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/
|
2298
|
-
os.
|
2299
|
-
|
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
|
-
|
2316
|
-
|
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
|
2333
|
-
|
2334
|
-
|
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
|
2367
|
-
|
2368
|
-
|
2369
|
-
|
2370
|
-
|
2371
|
-
|
2372
|
-
|
2373
|
-
|
2374
|
-
|
2375
|
-
|
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
|
-
|
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
|
-
|
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
|