gitarsenal-cli 1.1.3 → 1.1.5
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_README.md +145 -0
- package/python/__pycache__/gitarsenal_proxy_client.cpython-313.pyc +0 -0
- package/python/gitarsenal.py +87 -9
- package/python/gitarsenal_proxy_client.py +43 -10
- package/python/modal_proxy_service.py +65 -1
- package/python/test_modalSandboxScript.py +98 -1
package/package.json
CHANGED
@@ -0,0 +1,145 @@
|
|
1
|
+
# Modal Proxy Service for GitArsenal CLI
|
2
|
+
|
3
|
+
This document explains how to set up and use the Modal proxy service with GitArsenal CLI.
|
4
|
+
|
5
|
+
## What is the Modal Proxy Service?
|
6
|
+
|
7
|
+
The Modal proxy service allows users to access Modal services (like GPU-accelerated containers) without exposing the owner's Modal token. The service runs on a server and provides API endpoints for creating sandboxes and SSH containers, using API key authentication.
|
8
|
+
|
9
|
+
## Server-Side Setup
|
10
|
+
|
11
|
+
1. **Install Requirements**:
|
12
|
+
```bash
|
13
|
+
pip install flask flask-cors modal python-dotenv
|
14
|
+
```
|
15
|
+
|
16
|
+
2. **Set Environment Variables**:
|
17
|
+
Create a `.env` file in the same directory as `modal_proxy_service.py`:
|
18
|
+
```
|
19
|
+
MODAL_TOKEN=your_modal_token_here
|
20
|
+
ADMIN_KEY=your_admin_key_here
|
21
|
+
API_KEYS=optional_comma_separated_list_of_api_keys
|
22
|
+
```
|
23
|
+
|
24
|
+
3. **Start the Service**:
|
25
|
+
```bash
|
26
|
+
python modal_proxy_service.py
|
27
|
+
```
|
28
|
+
|
29
|
+
The service will start on port 5001 by default.
|
30
|
+
|
31
|
+
4. **Create an API Key** (if not pre-configured in `.env`):
|
32
|
+
```bash
|
33
|
+
curl -X POST http://localhost:5001/api/create-api-key \
|
34
|
+
-H "X-Admin-Key: your_admin_key_here"
|
35
|
+
```
|
36
|
+
|
37
|
+
This will return a JSON response with a new API key:
|
38
|
+
```json
|
39
|
+
{"api_key": "generated_api_key_here"}
|
40
|
+
```
|
41
|
+
|
42
|
+
5. **Optional: Use ngrok to expose the service publicly**:
|
43
|
+
```bash
|
44
|
+
ngrok http 5001
|
45
|
+
```
|
46
|
+
|
47
|
+
This will provide you with a public URL that you can share with users.
|
48
|
+
|
49
|
+
Current ngrok URL: `https://e74889c63199.ngrok-free.app`
|
50
|
+
|
51
|
+
## Client-Side Setup
|
52
|
+
|
53
|
+
1. **Configure the proxy client**:
|
54
|
+
```bash
|
55
|
+
./gitarsenal.py proxy configure
|
56
|
+
```
|
57
|
+
|
58
|
+
You'll be prompted to enter:
|
59
|
+
- The proxy URL (the default is now set to `https://e74889c63199.ngrok-free.app`)
|
60
|
+
- Your API key
|
61
|
+
|
62
|
+
2. **Check proxy service status**:
|
63
|
+
```bash
|
64
|
+
./gitarsenal.py proxy status
|
65
|
+
```
|
66
|
+
|
67
|
+
## Using the Proxy Service
|
68
|
+
|
69
|
+
### Create an SSH Container
|
70
|
+
|
71
|
+
```bash
|
72
|
+
# Using the proxy command
|
73
|
+
./gitarsenal.py proxy ssh --gpu A10G --repo-url https://github.com/username/repo.git --wait
|
74
|
+
|
75
|
+
# Or using the standard ssh command with --use-proxy flag
|
76
|
+
./gitarsenal.py ssh --use-proxy --gpu A10G --repo-url https://github.com/username/repo.git
|
77
|
+
```
|
78
|
+
|
79
|
+
### Create a Sandbox
|
80
|
+
|
81
|
+
```bash
|
82
|
+
# Using the proxy command
|
83
|
+
./gitarsenal.py proxy sandbox --gpu A10G --repo-url https://github.com/username/repo.git --wait
|
84
|
+
|
85
|
+
# Or using the standard sandbox command with --use-proxy flag
|
86
|
+
./gitarsenal.py sandbox --use-proxy --gpu A10G --repo-url https://github.com/username/repo.git
|
87
|
+
```
|
88
|
+
|
89
|
+
## Troubleshooting
|
90
|
+
|
91
|
+
### "Token missing" Error
|
92
|
+
|
93
|
+
If you see a "Token missing" error when creating containers through the proxy service:
|
94
|
+
|
95
|
+
1. **Check that the MODAL_TOKEN is set on the server**:
|
96
|
+
- Verify the `.env` file has a valid MODAL_TOKEN
|
97
|
+
- Ensure the token is correctly formatted and not expired
|
98
|
+
- Restart the proxy service after updating the token
|
99
|
+
|
100
|
+
2. **Manually set the token on the server**:
|
101
|
+
```bash
|
102
|
+
# On the server running the proxy service
|
103
|
+
export MODAL_TOKEN_ID=your_modal_token_here
|
104
|
+
modal token set your_modal_token_here
|
105
|
+
```
|
106
|
+
|
107
|
+
3. **Verify the token works directly on the server**:
|
108
|
+
```bash
|
109
|
+
# Test if the token works by listing Modal volumes
|
110
|
+
modal volume list
|
111
|
+
```
|
112
|
+
|
113
|
+
4. **Check the proxy service logs**:
|
114
|
+
- Look at `modal_proxy.log` on the server for detailed error messages
|
115
|
+
- Check for messages like "Modal token verification failed"
|
116
|
+
- Ensure the token is being properly passed to the Modal API
|
117
|
+
|
118
|
+
5. **Restart the proxy service**:
|
119
|
+
```bash
|
120
|
+
# Kill the existing process and restart
|
121
|
+
pkill -f modal_proxy_service.py
|
122
|
+
python modal_proxy_service.py
|
123
|
+
```
|
124
|
+
|
125
|
+
6. **Check ngrok connection**:
|
126
|
+
- Verify the ngrok tunnel is active and the URL is correct
|
127
|
+
- Ensure requests are being properly forwarded to the proxy service
|
128
|
+
|
129
|
+
### "Unauthorized" Error
|
130
|
+
|
131
|
+
If you see an "Unauthorized" error:
|
132
|
+
|
133
|
+
1. **When creating an API key**:
|
134
|
+
- Make sure you're including the correct admin key in the request header
|
135
|
+
- Example: `-H "X-Admin-Key: your_admin_key_here"`
|
136
|
+
|
137
|
+
2. **When using the API**:
|
138
|
+
- Make sure your client is configured with a valid API key
|
139
|
+
- Reconfigure with `./gitarsenal.py proxy configure`
|
140
|
+
|
141
|
+
## Security Considerations
|
142
|
+
|
143
|
+
- Keep your admin key and API keys secure
|
144
|
+
- Use HTTPS if exposing the service publicly (ngrok provides this automatically)
|
145
|
+
- Consider implementing additional authentication mechanisms for production use
|
Binary file
|
package/python/gitarsenal.py
CHANGED
@@ -60,21 +60,49 @@ def check_modal_auth():
|
|
60
60
|
return False
|
61
61
|
|
62
62
|
def check_proxy_config():
|
63
|
-
"""Check if Modal proxy is
|
63
|
+
"""Check if Modal proxy configuration exists and is valid"""
|
64
64
|
try:
|
65
|
-
# Import the proxy client
|
66
65
|
from gitarsenal_proxy_client import GitArsenalProxyClient
|
67
66
|
|
68
|
-
#
|
67
|
+
# Initialize client
|
69
68
|
client = GitArsenalProxyClient()
|
69
|
+
|
70
|
+
# Check if configuration exists
|
70
71
|
config = client.load_config()
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
if not config.get("proxy_url") or not config.get("api_key"):
|
74
|
+
print("⚠️ Modal proxy not configured. Setting up with default values...")
|
75
|
+
|
76
|
+
# Set default proxy URL to the ngrok URL
|
77
|
+
default_url = "https://e74889c63199.ngrok-free.app"
|
78
|
+
|
79
|
+
# Update configuration with default URL
|
80
|
+
config["proxy_url"] = default_url
|
81
|
+
client.save_config(config)
|
82
|
+
client.base_url = default_url
|
83
|
+
|
84
|
+
print(f"✅ Set default proxy URL: {default_url}")
|
85
|
+
|
86
|
+
# If API key is missing, prompt for configuration
|
87
|
+
if not config.get("api_key"):
|
88
|
+
print("⚠️ API key is still missing. Please configure:")
|
89
|
+
client.configure(interactive=True)
|
90
|
+
|
76
91
|
return False
|
92
|
+
|
93
|
+
# Verify connection to proxy
|
94
|
+
health = client.health_check()
|
95
|
+
if not health["success"]:
|
96
|
+
print(f"⚠️ Could not connect to Modal proxy at {config.get('proxy_url')}")
|
97
|
+
print(f" Error: {health.get('error', 'Unknown error')}")
|
98
|
+
print(" Run './gitarsenal.py proxy configure' to update configuration.")
|
99
|
+
return False
|
100
|
+
|
101
|
+
print(f"✅ Connected to Modal proxy at {config.get('proxy_url')}")
|
102
|
+
return True
|
103
|
+
|
77
104
|
except ImportError:
|
105
|
+
print("⚠️ GitArsenalProxyClient module not found")
|
78
106
|
return False
|
79
107
|
except Exception as e:
|
80
108
|
print(f"⚠️ Error checking proxy configuration: {e}")
|
@@ -384,7 +412,21 @@ def main():
|
|
384
412
|
from gitarsenal_proxy_client import GitArsenalProxyClient
|
385
413
|
client = GitArsenalProxyClient()
|
386
414
|
|
415
|
+
# Ensure proxy is configured
|
416
|
+
config = client.load_config()
|
417
|
+
if not config.get("proxy_url") or not config.get("api_key"):
|
418
|
+
print("\n⚠️ Modal proxy service is not fully configured.")
|
419
|
+
print("Running configuration wizard...")
|
420
|
+
client.configure(interactive=True)
|
421
|
+
# Reload config after configuration
|
422
|
+
config = client.load_config()
|
423
|
+
if not config.get("proxy_url") or not config.get("api_key"):
|
424
|
+
print("❌ Proxy configuration failed or was cancelled.")
|
425
|
+
return 1
|
426
|
+
print("✅ Proxy configuration completed successfully.")
|
427
|
+
|
387
428
|
# Create SSH container through proxy
|
429
|
+
print(f"🚀 Creating SSH container through proxy service ({config.get('proxy_url')})")
|
388
430
|
result = client.create_ssh_container(
|
389
431
|
gpu_type=args.gpu,
|
390
432
|
repo_url=args.repo_url,
|
@@ -475,6 +517,31 @@ def main():
|
|
475
517
|
|
476
518
|
elif args.proxy_command == "ssh":
|
477
519
|
# Create SSH container through proxy
|
520
|
+
print("🚀 Creating SSH container through proxy...")
|
521
|
+
|
522
|
+
# Verify proxy configuration is complete
|
523
|
+
config = client.load_config()
|
524
|
+
if not config.get("proxy_url") or not config.get("api_key"):
|
525
|
+
print("⚠️ Proxy configuration incomplete. Running configuration wizard...")
|
526
|
+
client.configure(interactive=True)
|
527
|
+
# Reload config after configuration
|
528
|
+
config = client.load_config()
|
529
|
+
if not config.get("proxy_url") or not config.get("api_key"):
|
530
|
+
print("❌ Proxy configuration failed or was cancelled.")
|
531
|
+
return 1
|
532
|
+
print("✅ Proxy configuration completed successfully.")
|
533
|
+
|
534
|
+
# Check proxy service health
|
535
|
+
health = client.health_check()
|
536
|
+
if not health["success"]:
|
537
|
+
print(f"❌ Could not connect to proxy service at {config.get('proxy_url')}")
|
538
|
+
print(f" Error: {health.get('error', 'Unknown error')}")
|
539
|
+
print(" Please check if the proxy service is running.")
|
540
|
+
return 1
|
541
|
+
|
542
|
+
print(f"✅ Connected to proxy service at {config.get('proxy_url')}")
|
543
|
+
|
544
|
+
# Create SSH container
|
478
545
|
result = client.create_ssh_container(
|
479
546
|
gpu_type=args.gpu,
|
480
547
|
repo_url=args.repo_url,
|
@@ -489,8 +556,19 @@ def main():
|
|
489
556
|
print("❌ Failed to create SSH container through proxy service")
|
490
557
|
return 1
|
491
558
|
|
492
|
-
|
493
|
-
|
559
|
+
# Print connection information if available
|
560
|
+
if isinstance(result, dict):
|
561
|
+
if "container_id" in result:
|
562
|
+
print(f"📋 Container ID: {result['container_id']}")
|
563
|
+
if "ssh_password" in result:
|
564
|
+
print(f"🔐 SSH Password: {result['ssh_password']}")
|
565
|
+
|
566
|
+
print("✅ SSH container creation process initiated through proxy service")
|
567
|
+
|
568
|
+
if not args.wait:
|
569
|
+
print("\n⚠️ Container creation is asynchronous. Use --wait flag to wait for it to be ready.")
|
570
|
+
print(" You can check the status later using the container ID above.")
|
571
|
+
|
494
572
|
else:
|
495
573
|
print(f"❌ Unknown proxy command: {args.proxy_command}")
|
496
574
|
proxy_parser.print_help()
|
@@ -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 = "
|
35
|
+
self.base_url = "https://e74889c63199.ngrok-free.app" # Default to ngrok URL
|
36
36
|
|
37
37
|
# Warn if no API key
|
38
38
|
if not self.api_key:
|
@@ -217,6 +217,28 @@ class GitArsenalProxyClient:
|
|
217
217
|
def create_ssh_container(self, gpu_type="A10G", repo_url=None, repo_name=None,
|
218
218
|
setup_commands=None, volume_name=None, timeout=60, wait=False):
|
219
219
|
"""Create a Modal SSH container through the proxy service"""
|
220
|
+
# Verify we have a valid API key
|
221
|
+
if not self.api_key:
|
222
|
+
print("❌ No API key provided. Please configure the proxy client first:")
|
223
|
+
print(" ./gitarsenal.py proxy configure")
|
224
|
+
return None
|
225
|
+
|
226
|
+
# Verify proxy URL is set
|
227
|
+
if not self.base_url:
|
228
|
+
print("❌ No proxy URL provided. Please configure the proxy client first:")
|
229
|
+
print(" ./gitarsenal.py proxy configure")
|
230
|
+
return None
|
231
|
+
|
232
|
+
# Check if proxy is reachable
|
233
|
+
health = self.health_check()
|
234
|
+
if not health["success"]:
|
235
|
+
print(f"❌ Could not connect to proxy service at {self.base_url}")
|
236
|
+
print(f" Error: {health.get('error', 'Unknown error')}")
|
237
|
+
print(" Please check if the proxy service is running and properly configured.")
|
238
|
+
return None
|
239
|
+
|
240
|
+
print(f"✅ Connected to proxy service at {self.base_url}")
|
241
|
+
|
220
242
|
data = {
|
221
243
|
"gpu_type": gpu_type,
|
222
244
|
"repo_url": repo_url,
|
@@ -226,10 +248,21 @@ class GitArsenalProxyClient:
|
|
226
248
|
"timeout": timeout
|
227
249
|
}
|
228
250
|
|
251
|
+
print("🔄 Sending request to create SSH container...")
|
229
252
|
response = self._make_request("post", "/api/create-ssh-container", data=data)
|
230
253
|
|
231
254
|
if not response["success"]:
|
232
255
|
print(f"❌ Failed to create SSH container: {response['error']}")
|
256
|
+
print(f" Status code: {response.get('status_code', 'Unknown')}")
|
257
|
+
|
258
|
+
# Additional error handling for common issues
|
259
|
+
if response.get('status_code') == 401:
|
260
|
+
print(" Authentication failed. Please check your API key.")
|
261
|
+
print(" Run './gitarsenal.py proxy configure' to set up a new API key.")
|
262
|
+
elif response.get('status_code') == 500:
|
263
|
+
print(" Server error. The proxy service might be misconfigured.")
|
264
|
+
print(" Check if the MODAL_TOKEN is properly set on the server.")
|
265
|
+
|
233
266
|
return None
|
234
267
|
|
235
268
|
container_id = response["data"].get("container_id")
|
@@ -253,21 +286,21 @@ class GitArsenalProxyClient:
|
|
253
286
|
status_data = status_response["data"]
|
254
287
|
if status_data.get("status") == "active":
|
255
288
|
print(f"✅ SSH container is ready!")
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
289
|
+
container_info = status_data.get("info", {})
|
290
|
+
|
291
|
+
# Add the password back since it's removed in the status endpoint
|
292
|
+
container_info["ssh_password"] = ssh_password
|
293
|
+
|
294
|
+
return container_info
|
261
295
|
|
262
296
|
print(".", end="", flush=True)
|
263
297
|
time.sleep(poll_interval)
|
264
298
|
|
265
299
|
print("\n⚠️ Timed out waiting for SSH container to be ready")
|
300
|
+
print("The container may still be initializing. Check status with:")
|
301
|
+
print(f"./gitarsenal.py proxy status {container_id}")
|
266
302
|
|
267
|
-
return {
|
268
|
-
"container_id": container_id,
|
269
|
-
"ssh_password": ssh_password
|
270
|
-
}
|
303
|
+
return {"container_id": container_id, "ssh_password": ssh_password}
|
271
304
|
|
272
305
|
def get_container_status(self, container_id):
|
273
306
|
"""Get the status of a container"""
|
@@ -82,7 +82,30 @@ 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
|
-
|
85
|
+
# Also set the token directly in modal.config
|
86
|
+
modal.config._auth_config.token_id = MODAL_TOKEN
|
87
|
+
|
88
|
+
# Verify token is working by attempting a simple operation
|
89
|
+
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
|
+
)
|
102
|
+
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
|
107
|
+
|
108
|
+
logger.info("Modal token set in environment and config")
|
86
109
|
return True
|
87
110
|
except Exception as e:
|
88
111
|
logger.error(f"Error setting up Modal authentication: {e}")
|
@@ -133,6 +156,11 @@ def create_sandbox():
|
|
133
156
|
# Start sandbox creation in a separate thread
|
134
157
|
def create_sandbox_thread():
|
135
158
|
try:
|
159
|
+
# Ensure Modal token is set before creating sandbox
|
160
|
+
if not setup_modal_auth():
|
161
|
+
logger.error("Failed to set up Modal authentication in thread")
|
162
|
+
return
|
163
|
+
|
136
164
|
result = create_modal_sandbox(
|
137
165
|
gpu_type,
|
138
166
|
repo_url=repo_url,
|
@@ -196,9 +224,45 @@ def create_ssh_container():
|
|
196
224
|
# Create a unique ID for this container
|
197
225
|
container_id = str(uuid.uuid4())
|
198
226
|
|
227
|
+
# Log token status for debugging
|
228
|
+
token_status = "Token is set" if MODAL_TOKEN else "Token is missing"
|
229
|
+
logger.info(f"Modal token status before thread: {token_status}")
|
230
|
+
logger.info(f"Modal token length: {len(MODAL_TOKEN) if MODAL_TOKEN else 'N/A'}")
|
231
|
+
|
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
|
+
|
199
239
|
# Start container creation in a separate thread
|
200
240
|
def create_container_thread():
|
201
241
|
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
|
248
|
+
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}")
|
265
|
+
|
202
266
|
result = create_modal_ssh_container(
|
203
267
|
gpu_type,
|
204
268
|
repo_url=repo_url,
|
@@ -2067,6 +2067,59 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
2067
2067
|
volume_name=None, timeout_minutes=60, ssh_password=None):
|
2068
2068
|
"""Create a Modal SSH container with GPU support and tunneling"""
|
2069
2069
|
|
2070
|
+
# Check if Modal is authenticated
|
2071
|
+
try:
|
2072
|
+
# Try to access Modal token to check authentication
|
2073
|
+
try:
|
2074
|
+
# Check if token is set in environment
|
2075
|
+
modal_token = os.environ.get("MODAL_TOKEN_ID")
|
2076
|
+
if not modal_token:
|
2077
|
+
print("⚠️ MODAL_TOKEN_ID not found in environment.")
|
2078
|
+
# Try to set from modal.config if available
|
2079
|
+
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
|
2085
|
+
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
|
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
|
2119
|
+
except Exception as e:
|
2120
|
+
print(f"⚠️ Error checking Modal authentication: {e}")
|
2121
|
+
print("Continuing anyway, but Modal operations may fail")
|
2122
|
+
|
2070
2123
|
# Generate a unique app name with timestamp to avoid conflicts
|
2071
2124
|
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
2072
2125
|
app_name = f"ssh-container-{timestamp}"
|
@@ -2116,6 +2169,41 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
2116
2169
|
print(f"⚠️ Could not create default volume: {e}")
|
2117
2170
|
print("⚠️ Continuing without persistent volume")
|
2118
2171
|
volume = None
|
2172
|
+
|
2173
|
+
# Print debug info for authentication
|
2174
|
+
print("🔍 Modal authentication debug info:")
|
2175
|
+
modal_token = os.environ.get("MODAL_TOKEN_ID")
|
2176
|
+
print(f" - MODAL_TOKEN_ID in env: {'Yes' if modal_token else 'No'}")
|
2177
|
+
print(f" - Token length: {len(modal_token) if modal_token else 'N/A'}")
|
2178
|
+
|
2179
|
+
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
|
2204
|
+
except Exception as e:
|
2205
|
+
print(f"❌ Error during token validation: {e}")
|
2206
|
+
return None
|
2119
2207
|
|
2120
2208
|
# Create SSH-enabled image
|
2121
2209
|
ssh_image = (
|
@@ -2153,7 +2241,12 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
2153
2241
|
)
|
2154
2242
|
|
2155
2243
|
# Create the Modal app
|
2156
|
-
|
2244
|
+
try:
|
2245
|
+
app = modal.App(app_name)
|
2246
|
+
print("✅ Created Modal app successfully")
|
2247
|
+
except Exception as e:
|
2248
|
+
print(f"❌ Error creating Modal app: {e}")
|
2249
|
+
return None
|
2157
2250
|
|
2158
2251
|
# Configure volumes if available
|
2159
2252
|
volumes_config = {}
|
@@ -2319,6 +2412,10 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
2319
2412
|
"volume_name": volume_name,
|
2320
2413
|
"volume_mount_path": volume_mount_path if volume else None
|
2321
2414
|
}
|
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
|
2322
2419
|
except KeyboardInterrupt:
|
2323
2420
|
print("\n👋 Container startup interrupted")
|
2324
2421
|
return None
|