gitarsenal-cli 1.7.3 → 1.7.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.
Binary file
|
package/package.json
CHANGED
@@ -30,11 +30,9 @@ args, unknown = parser.parse_known_args()
|
|
30
30
|
# Set proxy URL and API key in environment variables if provided
|
31
31
|
if args.proxy_url:
|
32
32
|
os.environ["MODAL_PROXY_URL"] = args.proxy_url
|
33
|
-
# print(f"✅ Set MODAL_PROXY_URL from command line")
|
34
33
|
|
35
34
|
if args.proxy_api_key:
|
36
35
|
os.environ["MODAL_PROXY_API_KEY"] = args.proxy_api_key
|
37
|
-
# print(f"✅ Set MODAL_PROXY_API_KEY from command line")
|
38
36
|
|
39
37
|
class PersistentShell:
|
40
38
|
"""A persistent bash shell using subprocess.Popen for executing commands with state persistence."""
|
@@ -446,26 +444,20 @@ except Exception as e:
|
|
446
444
|
# Apply the comprehensive Modal token solution as fallback
|
447
445
|
try:
|
448
446
|
# Import the comprehensive solution module
|
449
|
-
# print("🔄 Applying comprehensive Modal token solution...")
|
450
447
|
import modal_token_solution
|
451
|
-
# print("✅ Comprehensive Modal token solution applied")
|
452
448
|
|
453
449
|
# Set token variables for later use
|
454
450
|
token = modal_token_solution.TOKEN_ID # For backward compatibility
|
455
451
|
except Exception as e:
|
456
|
-
# print(f"⚠️ Error applying comprehensive Modal token solution: {e}")
|
457
452
|
|
458
453
|
# Fall back to the authentication patch
|
459
454
|
try:
|
460
455
|
# Import the patch module
|
461
|
-
# print("🔄 Falling back to Modal authentication patch...")
|
462
456
|
import modal_auth_patch
|
463
|
-
# print("✅ Modal authentication patch applied")
|
464
457
|
|
465
458
|
# Set token variables for later use
|
466
459
|
token = modal_auth_patch.TOKEN_ID # For backward compatibility
|
467
460
|
except Exception as e:
|
468
|
-
# print(f"⚠️ Error applying Modal authentication patch: {e}")
|
469
461
|
|
470
462
|
# Fall back to fix_modal_token.py
|
471
463
|
try:
|
@@ -500,22 +492,14 @@ except Exception as e:
|
|
500
492
|
token = "ak-sLhYqCjkvixiYcb9LAuCHp" # Default token ID
|
501
493
|
except Exception as e:
|
502
494
|
print(f"⚠️ Error running fix_modal_token.py: {e}")
|
503
|
-
|
504
|
-
# Last resort: use hardcoded tokens
|
505
495
|
token = "ak-sLhYqCjkvixiYcb9LAuCHp" # Default token ID
|
506
496
|
|
507
|
-
# Print debug info
|
508
|
-
# print(f"🔍 DEBUG: Checking environment variables")
|
509
|
-
# print(f"🔍 Token ID exists: {'Yes' if os.environ.get('MODAL_TOKEN_ID') else 'No'}")
|
510
|
-
# print(f"🔍 Token secret exists: {'Yes' if os.environ.get('MODAL_TOKEN_SECRET') else 'No'}")
|
511
|
-
# print(f"🔍 Token exists: {'Yes' if os.environ.get('MODAL_TOKEN') else 'No'}")
|
512
497
|
if os.environ.get('MODAL_TOKEN_ID'):
|
513
498
|
print(f"🔍 Token ID length: {len(os.environ.get('MODAL_TOKEN_ID'))}")
|
514
499
|
if os.environ.get('MODAL_TOKEN_SECRET'):
|
515
500
|
print(f"🔍 Token secret length: {len(os.environ.get('MODAL_TOKEN_SECRET'))}")
|
516
501
|
if os.environ.get('MODAL_TOKEN'):
|
517
502
|
print(f"🔍 Token length: {len(os.environ.get('MODAL_TOKEN'))}")
|
518
|
-
# print(f"✅ Token setup completed")
|
519
503
|
|
520
504
|
# Import modal after token setup
|
521
505
|
import modal
|
@@ -544,168 +528,6 @@ def handle_interactive_input(prompt, is_password=False):
|
|
544
528
|
print(f"❌ Error getting input: {e}")
|
545
529
|
return None
|
546
530
|
|
547
|
-
def handle_wandb_login(sandbox, current_dir):
|
548
|
-
"""Handle Weights & Biases login with proper API key input"""
|
549
|
-
# Define _to_str function locally to avoid NameError
|
550
|
-
def _to_str(maybe_bytes):
|
551
|
-
try:
|
552
|
-
return (maybe_bytes.decode('utf-8') if isinstance(maybe_bytes, (bytes, bytearray)) else maybe_bytes)
|
553
|
-
except UnicodeDecodeError:
|
554
|
-
# Handle non-UTF-8 bytes by replacing invalid characters
|
555
|
-
if isinstance(maybe_bytes, (bytes, bytearray)):
|
556
|
-
return maybe_bytes.decode('utf-8', errors='replace')
|
557
|
-
else:
|
558
|
-
return str(maybe_bytes)
|
559
|
-
except Exception:
|
560
|
-
# Last resort fallback
|
561
|
-
return str(maybe_bytes)
|
562
|
-
|
563
|
-
print("\n🔑 WEIGHTS & BIASES LOGIN")
|
564
|
-
print("="*60)
|
565
|
-
print("Setting up Weights & Biases credentials")
|
566
|
-
print("You can get your API key from: https://wandb.ai/authorize")
|
567
|
-
|
568
|
-
# Try to use credentials manager first
|
569
|
-
api_key = None
|
570
|
-
try:
|
571
|
-
from credentials_manager import CredentialsManager
|
572
|
-
credentials_manager = CredentialsManager()
|
573
|
-
api_key = credentials_manager.get_wandb_api_key()
|
574
|
-
except ImportError:
|
575
|
-
# Fall back to direct input if credentials_manager is not available
|
576
|
-
pass
|
577
|
-
|
578
|
-
# If credentials manager didn't provide a key, use direct input
|
579
|
-
if not api_key:
|
580
|
-
# Get API key from user
|
581
|
-
api_key = handle_interactive_input(
|
582
|
-
"🔑 WEIGHTS & BIASES API KEY REQUIRED\n" +
|
583
|
-
"Please paste your W&B API key below:\n" +
|
584
|
-
"(Your API key should be 40 characters long)",
|
585
|
-
is_password=True
|
586
|
-
)
|
587
|
-
|
588
|
-
if not api_key:
|
589
|
-
print("❌ No API key provided. Cannot continue with W&B login.")
|
590
|
-
return False, "", "No W&B API key provided"
|
591
|
-
|
592
|
-
# Validate API key length
|
593
|
-
if len(api_key) != 40:
|
594
|
-
print(f"⚠️ Warning: API key should be 40 characters long, yours was {len(api_key)}")
|
595
|
-
confirm = handle_interactive_input("Continue anyway? (yes/no)")
|
596
|
-
if not confirm or confirm.lower() not in ["yes", "y"]:
|
597
|
-
print("❌ W&B login cancelled.")
|
598
|
-
return False, "", "W&B login cancelled"
|
599
|
-
|
600
|
-
# Use non-interactive login
|
601
|
-
cmd = f"wandb login {api_key}"
|
602
|
-
print(f"🔄 Running non-interactive login command")
|
603
|
-
|
604
|
-
# Execute the command
|
605
|
-
result = sandbox.exec("bash", "-c", f"cd {current_dir} && {cmd}")
|
606
|
-
|
607
|
-
# Collect output
|
608
|
-
stdout_lines = []
|
609
|
-
stderr_lines = []
|
610
|
-
|
611
|
-
for line in result.stdout:
|
612
|
-
line_str = _to_str(line)
|
613
|
-
stdout_lines.append(line_str)
|
614
|
-
sys.stdout.write(line_str)
|
615
|
-
sys.stdout.flush()
|
616
|
-
|
617
|
-
for line in result.stderr:
|
618
|
-
line_str = _to_str(line)
|
619
|
-
stderr_lines.append(line_str)
|
620
|
-
sys.stderr.write(line_str)
|
621
|
-
sys.stderr.flush()
|
622
|
-
|
623
|
-
result.wait()
|
624
|
-
exit_code = result.returncode
|
625
|
-
|
626
|
-
stdout_buffer = ''.join(stdout_lines)
|
627
|
-
stderr_buffer = ''.join(stderr_lines)
|
628
|
-
|
629
|
-
if exit_code == 0:
|
630
|
-
print("✅ Weights & Biases login successful")
|
631
|
-
# Also set the environment variable for this session
|
632
|
-
os.environ["WANDB_API_KEY"] = api_key
|
633
|
-
print("✅ WANDB_API_KEY environment variable set")
|
634
|
-
else:
|
635
|
-
print(f"❌ Weights & Biases login failed with exit code {exit_code}")
|
636
|
-
if stderr_buffer:
|
637
|
-
print(f"Error: {stderr_buffer}")
|
638
|
-
|
639
|
-
return exit_code == 0, stdout_buffer, stderr_buffer
|
640
|
-
|
641
|
-
def handle_huggingface_login(sandbox, current_dir):
|
642
|
-
"""Handle Hugging Face login with proper token input"""
|
643
|
-
# Define _to_str function locally to avoid NameError
|
644
|
-
def _to_str(maybe_bytes):
|
645
|
-
try:
|
646
|
-
return (maybe_bytes.decode('utf-8') if isinstance(maybe_bytes, (bytes, bytearray)) else maybe_bytes)
|
647
|
-
except UnicodeDecodeError:
|
648
|
-
# Handle non-UTF-8 bytes by replacing invalid characters
|
649
|
-
if isinstance(maybe_bytes, (bytes, bytearray)):
|
650
|
-
return maybe_bytes.decode('utf-8', errors='replace')
|
651
|
-
else:
|
652
|
-
return str(maybe_bytes)
|
653
|
-
except Exception:
|
654
|
-
# Last resort fallback
|
655
|
-
return str(maybe_bytes)
|
656
|
-
|
657
|
-
print("\n🔑 HUGGING FACE LOGIN")
|
658
|
-
print("="*60)
|
659
|
-
print("Setting up Hugging Face credentials")
|
660
|
-
|
661
|
-
# Get token from user
|
662
|
-
token = prompt_for_hf_token()
|
663
|
-
if not token:
|
664
|
-
print("❌ No token provided. Cannot continue with Hugging Face login.")
|
665
|
-
return False, "", "No Hugging Face token provided"
|
666
|
-
|
667
|
-
# Use non-interactive login
|
668
|
-
cmd = f"huggingface-cli login --token {token} --add-to-git-credential"
|
669
|
-
print(f"🔄 Running non-interactive login command")
|
670
|
-
|
671
|
-
# Execute the command
|
672
|
-
result = sandbox.exec("bash", "-c", f"cd {current_dir} && {cmd}")
|
673
|
-
|
674
|
-
# Collect output
|
675
|
-
stdout_lines = []
|
676
|
-
stderr_lines = []
|
677
|
-
|
678
|
-
for line in result.stdout:
|
679
|
-
line_str = _to_str(line)
|
680
|
-
stdout_lines.append(line_str)
|
681
|
-
sys.stdout.write(line_str)
|
682
|
-
sys.stdout.flush()
|
683
|
-
|
684
|
-
for line in result.stderr:
|
685
|
-
line_str = _to_str(line)
|
686
|
-
stderr_lines.append(line_str)
|
687
|
-
sys.stderr.write(line_str)
|
688
|
-
sys.stderr.flush()
|
689
|
-
|
690
|
-
result.wait()
|
691
|
-
exit_code = result.returncode
|
692
|
-
|
693
|
-
stdout_buffer = ''.join(stdout_lines)
|
694
|
-
stderr_buffer = ''.join(stderr_lines)
|
695
|
-
|
696
|
-
if exit_code == 0:
|
697
|
-
print("✅ Hugging Face login successful")
|
698
|
-
# Also set the environment variable for this session
|
699
|
-
os.environ["HF_TOKEN"] = token
|
700
|
-
print("✅ HF_TOKEN environment variable set")
|
701
|
-
else:
|
702
|
-
print(f"❌ Hugging Face login failed with exit code {exit_code}")
|
703
|
-
if stderr_buffer:
|
704
|
-
print(f"Error: {stderr_buffer}")
|
705
|
-
|
706
|
-
return exit_code == 0, stdout_buffer, stderr_buffer
|
707
|
-
|
708
|
-
|
709
531
|
def get_stored_credentials():
|
710
532
|
"""Load stored credentials from ~/.gitarsenal/credentials.json"""
|
711
533
|
import json
|
@@ -788,8 +610,6 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
|
|
788
610
|
from fetch_modal_tokens import get_tokens
|
789
611
|
_, _, api_key = get_tokens()
|
790
612
|
if api_key:
|
791
|
-
# print("✅ Successfully fetched OpenAI API key from server")
|
792
|
-
# print(f"🔍 DEBUG: Fetched OpenAI API key value: {api_key}")
|
793
613
|
# Set in environment for this session
|
794
614
|
os.environ["OPENAI_API_KEY"] = api_key
|
795
615
|
else:
|
@@ -930,29 +750,25 @@ System Information:
|
|
930
750
|
print("🔍 Getting directory context for better debugging...")
|
931
751
|
|
932
752
|
# Get current directory contents
|
933
|
-
ls_result = sandbox.exec("bash", "-c",
|
753
|
+
ls_result = sandbox.exec("bash", "-c", "ls -la")
|
934
754
|
ls_output = ""
|
935
755
|
for line in ls_result.stdout:
|
936
756
|
ls_output += _to_str(line)
|
937
757
|
ls_result.wait()
|
938
758
|
|
939
|
-
# Get parent directory contents
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
parent_ls += _to_str(line)
|
947
|
-
parent_result.wait()
|
948
|
-
parent_context = f"\nParent directory ({parent_dir}) contents:\n{parent_ls}"
|
949
|
-
|
759
|
+
# Get parent directory contents
|
760
|
+
parent_result = sandbox.exec("bash", "-c", "ls -la ../")
|
761
|
+
parent_ls = ""
|
762
|
+
for line in parent_result.stdout:
|
763
|
+
parent_ls += _to_str(line)
|
764
|
+
parent_result.wait()
|
765
|
+
|
950
766
|
directory_context = f"""
|
951
|
-
Current directory:
|
952
|
-
|
953
|
-
Directory contents:
|
767
|
+
Current directory contents:
|
954
768
|
{ls_output}
|
955
|
-
|
769
|
+
|
770
|
+
Parent directory contents:
|
771
|
+
{parent_ls}
|
956
772
|
"""
|
957
773
|
print("✅ Directory context gathered successfully")
|
958
774
|
|
@@ -966,11 +782,11 @@ Directory contents:
|
|
966
782
|
file_path = file_path.strip("'\"")
|
967
783
|
if not os.path.isabs(file_path):
|
968
784
|
file_path = os.path.join(current_dir, file_path)
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
785
|
+
|
786
|
+
# Try to get the parent directory if the file doesn't exist
|
787
|
+
if '/' in file_path:
|
788
|
+
parent_file_dir = os.path.dirname(file_path)
|
789
|
+
relevant_files.append(parent_file_dir)
|
974
790
|
|
975
791
|
# Look for package.json, requirements.txt, etc.
|
976
792
|
common_config_files = ["package.json", "requirements.txt", "pyproject.toml", "setup.py",
|
@@ -1033,6 +849,12 @@ Directory contents:
|
|
1033
849
|
|
1034
850
|
stored_credentials = get_stored_credentials()
|
1035
851
|
auth_context = generate_auth_context(stored_credentials)
|
852
|
+
|
853
|
+
|
854
|
+
print("DEBUG: AUTH_CONTEXT SENT TO LLM:")
|
855
|
+
print("="*60)
|
856
|
+
print(auth_context)
|
857
|
+
print("="*60 + "\n")
|
1036
858
|
|
1037
859
|
# Create a prompt for the LLM
|
1038
860
|
print("\n" + "="*60)
|
@@ -1081,17 +903,25 @@ IMPORTANT GUIDELINES:
|
|
1081
903
|
|
1082
904
|
4. For authentication issues:
|
1083
905
|
- Analyze the error to determine what type of authentication is needed
|
1084
|
-
-
|
906
|
+
- ALWAYS use the actual credential values from the AVAILABLE CREDENTIALS section above (NOT placeholders)
|
907
|
+
- Look for the specific API key or token needed in the auth_context and use its exact value
|
1085
908
|
- Common patterns:
|
1086
|
-
* wandb errors: use wandb login with WANDB_API_KEY
|
1087
|
-
* huggingface errors: use huggingface-cli login with HF_TOKEN or HUGGINGFACE_TOKEN
|
1088
|
-
* github errors: configure git credentials with GITHUB_TOKEN
|
1089
|
-
* kaggle errors: create ~/.kaggle/kaggle.json with KAGGLE_USERNAME and KAGGLE_KEY
|
1090
|
-
* API errors: export the appropriate API key as environment variable
|
909
|
+
* wandb errors: use wandb login with the actual WANDB_API_KEY value from auth_context
|
910
|
+
* huggingface errors: use huggingface-cli login with the actual HF_TOKEN or HUGGINGFACE_TOKEN value from auth_context
|
911
|
+
* github errors: configure git credentials with the actual GITHUB_TOKEN value from auth_context
|
912
|
+
* kaggle errors: create ~/.kaggle/kaggle.json with the actual KAGGLE_USERNAME and KAGGLE_KEY values from auth_context
|
913
|
+
* API errors: export the appropriate API key as environment variable using the actual value from auth_context
|
1091
914
|
|
1092
915
|
5. Environment variable exports:
|
1093
916
|
- Use export commands for API keys that need to be in environment
|
1094
|
-
-
|
917
|
+
- ALWAYS use the actual credential values from auth_context, never use placeholders like "YOUR_API_KEY"
|
918
|
+
- Example: export OPENAI_API_KEY="sk-..." (using the actual key from auth_context)
|
919
|
+
|
920
|
+
6. CRITICAL: When using any API key, token, or credential:
|
921
|
+
- Find the exact value in the AVAILABLE CREDENTIALS section
|
922
|
+
- Use that exact value in your command
|
923
|
+
- Do not use generic placeholders or dummy values
|
924
|
+
- The auth_context contains real, usable credentials
|
1095
925
|
|
1096
926
|
Do not provide any explanations, just the exact command to run.
|
1097
927
|
"""
|
@@ -1362,31 +1192,6 @@ def prompt_for_hf_token():
|
|
1362
1192
|
print(f"❌ Error getting token: {e}")
|
1363
1193
|
return None
|
1364
1194
|
|
1365
|
-
|
1366
|
-
def handle_interactive_input(prompt, is_password=False):
|
1367
|
-
"""Handle interactive input from the user with optional password masking"""
|
1368
|
-
print("\n" + "="*60)
|
1369
|
-
print(f"{prompt}")
|
1370
|
-
print("="*60)
|
1371
|
-
|
1372
|
-
try:
|
1373
|
-
if is_password:
|
1374
|
-
user_input = getpass.getpass("Input (hidden): ").strip()
|
1375
|
-
else:
|
1376
|
-
user_input = input("Input: ").strip()
|
1377
|
-
|
1378
|
-
if not user_input:
|
1379
|
-
print("❌ No input provided.")
|
1380
|
-
return None
|
1381
|
-
print("✅ Input received successfully!")
|
1382
|
-
return user_input
|
1383
|
-
except KeyboardInterrupt:
|
1384
|
-
print("\n❌ Input cancelled by user.")
|
1385
|
-
return None
|
1386
|
-
except Exception as e:
|
1387
|
-
print(f"❌ Error getting input: {e}")
|
1388
|
-
return None
|
1389
|
-
|
1390
1195
|
def generate_random_password(length=16):
|
1391
1196
|
"""Generate a random password for SSH access"""
|
1392
1197
|
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
|
@@ -1618,7 +1423,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1618
1423
|
"python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
|
1619
1424
|
"gpg", "ca-certificates", "software-properties-common"
|
1620
1425
|
)
|
1621
|
-
.
|
1426
|
+
.pip_install("uv", "modal", "requests", "openai") # Remove problematic CUDA packages
|
1622
1427
|
.run_commands(
|
1623
1428
|
# Create SSH directory
|
1624
1429
|
"mkdir -p /var/run/sshd",
|
@@ -1668,11 +1473,12 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1668
1473
|
serialized=True,
|
1669
1474
|
volumes=volumes_config if volumes_config else None,
|
1670
1475
|
)
|
1671
|
-
def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None):
|
1476
|
+
def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None, stored_credentials=None):
|
1672
1477
|
"""Start SSH container with password authentication and optional setup."""
|
1673
1478
|
import subprocess
|
1674
1479
|
import time
|
1675
1480
|
import os
|
1481
|
+
import json
|
1676
1482
|
|
1677
1483
|
# Set root password
|
1678
1484
|
subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
|
@@ -1680,37 +1486,52 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1680
1486
|
# Set OpenAI API key if provided
|
1681
1487
|
if openai_api_key:
|
1682
1488
|
os.environ['OPENAI_API_KEY'] = openai_api_key
|
1683
|
-
print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
|
1489
|
+
# print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
|
1684
1490
|
else:
|
1685
1491
|
print("⚠️ No OpenAI API key provided to container")
|
1686
1492
|
|
1687
|
-
#
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1493
|
+
# Set up stored credentials in container environment
|
1494
|
+
if stored_credentials:
|
1495
|
+
print(f"🔐 Setting up {len(stored_credentials)} stored credentials in container...")
|
1496
|
+
for key, value in stored_credentials.items():
|
1497
|
+
# Set each credential as an environment variable
|
1498
|
+
env_var_name = key.upper().replace('-', '_').replace(' ', '_')
|
1499
|
+
os.environ[env_var_name] = value
|
1500
|
+
print(f"✅ Set {env_var_name} in container environment")
|
1695
1501
|
|
1502
|
+
# Also save credentials to a file in the container for easy access
|
1696
1503
|
try:
|
1697
|
-
|
1698
|
-
|
1504
|
+
credentials_dir = "/root/.gitarsenal"
|
1505
|
+
os.makedirs(credentials_dir, exist_ok=True)
|
1506
|
+
credentials_file = os.path.join(credentials_dir, "credentials.json")
|
1507
|
+
with open(credentials_file, 'w') as f:
|
1508
|
+
json.dump(stored_credentials, f, indent=2)
|
1509
|
+
print(f"✅ Saved credentials to {credentials_file}")
|
1699
1510
|
|
1700
|
-
#
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1511
|
+
# Print available credentials for user reference
|
1512
|
+
print("\n🔐 AVAILABLE CREDENTIALS IN CONTAINER:")
|
1513
|
+
print("="*50)
|
1514
|
+
for key, value in stored_credentials.items():
|
1515
|
+
masked_value = value[:8] + "..." if len(value) > 8 else "***"
|
1516
|
+
env_var_name = key.upper().replace('-', '_').replace(' ', '_')
|
1517
|
+
print(f" {key} -> {env_var_name} = {masked_value}")
|
1518
|
+
print("="*50)
|
1519
|
+
print("💡 These credentials are available as environment variables and in /root/.gitarsenal/credentials.json")
|
1704
1520
|
|
1705
|
-
except
|
1706
|
-
print(f"
|
1521
|
+
except Exception as e:
|
1522
|
+
print(f"⚠️ Could not save credentials file: {e}")
|
1523
|
+
else:
|
1524
|
+
print("⚠️ No stored credentials provided to container")
|
1525
|
+
|
1526
|
+
# Start SSH service
|
1527
|
+
subprocess.run(["service", "ssh", "start"], check=True)
|
1707
1528
|
|
1708
1529
|
# Run setup commands if provided using PersistentShell
|
1709
1530
|
if setup_commands:
|
1710
1531
|
print(f"⚙️ Running {len(setup_commands)} setup commands with persistent shell...")
|
1711
1532
|
|
1712
|
-
# Create persistent shell instance
|
1713
|
-
shell = PersistentShell(working_dir=
|
1533
|
+
# Create persistent shell instance starting in /root
|
1534
|
+
shell = PersistentShell(working_dir="/root", timeout=120)
|
1714
1535
|
|
1715
1536
|
try:
|
1716
1537
|
# Start the persistent shell
|
@@ -1808,7 +1629,15 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1808
1629
|
with app.run():
|
1809
1630
|
# Get the API key from environment
|
1810
1631
|
api_key = os.environ.get("OPENAI_API_KEY")
|
1811
|
-
|
1632
|
+
|
1633
|
+
# Get stored credentials from local file
|
1634
|
+
stored_credentials = get_stored_credentials()
|
1635
|
+
if stored_credentials:
|
1636
|
+
print(f"🔐 Found {len(stored_credentials)} stored credentials to send to container")
|
1637
|
+
else:
|
1638
|
+
print("⚠️ No stored credentials found")
|
1639
|
+
|
1640
|
+
ssh_container_function.remote(ssh_password, repo_url, repo_name, setup_commands, api_key, stored_credentials)
|
1812
1641
|
|
1813
1642
|
# Clean up Modal token after container is successfully created
|
1814
1643
|
cleanup_modal_token()
|
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Test script to verify that stored credentials are being passed correctly to SSH containers.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
import json
|
8
|
+
from pathlib import Path
|
9
|
+
|
10
|
+
def test_credentials_loading():
|
11
|
+
"""Test that credentials can be loaded from the local file"""
|
12
|
+
print("🔍 Testing credentials loading...")
|
13
|
+
|
14
|
+
# Test get_stored_credentials function
|
15
|
+
try:
|
16
|
+
from test_modalSandboxScript import get_stored_credentials
|
17
|
+
credentials = get_stored_credentials()
|
18
|
+
|
19
|
+
if credentials:
|
20
|
+
print(f"✅ Found {len(credentials)} stored credentials:")
|
21
|
+
for key, value in credentials.items():
|
22
|
+
masked_value = value[:8] + "..." if len(value) > 8 else "***"
|
23
|
+
print(f" - {key}: {masked_value}")
|
24
|
+
else:
|
25
|
+
print("⚠️ No stored credentials found")
|
26
|
+
|
27
|
+
return credentials
|
28
|
+
except Exception as e:
|
29
|
+
print(f"❌ Error loading credentials: {e}")
|
30
|
+
return {}
|
31
|
+
|
32
|
+
def test_credentials_file():
|
33
|
+
"""Test that the credentials file exists and is readable"""
|
34
|
+
print("\n🔍 Testing credentials file...")
|
35
|
+
|
36
|
+
credentials_file = Path.home() / ".gitarsenal" / "credentials.json"
|
37
|
+
|
38
|
+
if credentials_file.exists():
|
39
|
+
print(f"✅ Credentials file exists at: {credentials_file}")
|
40
|
+
try:
|
41
|
+
with open(credentials_file, 'r') as f:
|
42
|
+
credentials = json.load(f)
|
43
|
+
print(f"✅ Successfully loaded {len(credentials)} credentials from file")
|
44
|
+
return credentials
|
45
|
+
except Exception as e:
|
46
|
+
print(f"❌ Error reading credentials file: {e}")
|
47
|
+
return {}
|
48
|
+
else:
|
49
|
+
print(f"⚠️ Credentials file not found at: {credentials_file}")
|
50
|
+
print("💡 You can create it by running the credentials manager")
|
51
|
+
return {}
|
52
|
+
|
53
|
+
def test_environment_variables():
|
54
|
+
"""Test that credentials are available as environment variables"""
|
55
|
+
print("\n🔍 Testing environment variables...")
|
56
|
+
|
57
|
+
credential_env_vars = [
|
58
|
+
'OPENAI_API_KEY', 'WANDB_API_KEY', 'HF_TOKEN', 'HUGGINGFACE_TOKEN',
|
59
|
+
'GITHUB_TOKEN', 'KAGGLE_USERNAME', 'KAGGLE_KEY'
|
60
|
+
]
|
61
|
+
|
62
|
+
found_creds = {}
|
63
|
+
for env_var in credential_env_vars:
|
64
|
+
value = os.environ.get(env_var)
|
65
|
+
if value:
|
66
|
+
found_creds[env_var] = value
|
67
|
+
masked_value = value[:8] + "..." if len(value) > 8 else "***"
|
68
|
+
print(f"✅ {env_var}: {masked_value}")
|
69
|
+
|
70
|
+
if not found_creds:
|
71
|
+
print("⚠️ No credential environment variables found")
|
72
|
+
|
73
|
+
return found_creds
|
74
|
+
|
75
|
+
def test_credentials_integration():
|
76
|
+
"""Test the full credentials integration"""
|
77
|
+
print("\n" + "="*60)
|
78
|
+
print("🔐 CREDENTIALS INTEGRATION TEST")
|
79
|
+
print("="*60)
|
80
|
+
|
81
|
+
# Test all credential sources
|
82
|
+
file_creds = test_credentials_file()
|
83
|
+
env_creds = test_environment_variables()
|
84
|
+
function_creds = test_credentials_loading()
|
85
|
+
|
86
|
+
# Combine all credentials
|
87
|
+
all_creds = {}
|
88
|
+
all_creds.update(file_creds)
|
89
|
+
all_creds.update(env_creds)
|
90
|
+
all_creds.update(function_creds)
|
91
|
+
|
92
|
+
print(f"\n📊 SUMMARY:")
|
93
|
+
print(f" - File credentials: {len(file_creds)}")
|
94
|
+
print(f" - Environment credentials: {len(env_creds)}")
|
95
|
+
print(f" - Function credentials: {len(function_creds)}")
|
96
|
+
print(f" - Total unique credentials: {len(all_creds)}")
|
97
|
+
|
98
|
+
if all_creds:
|
99
|
+
print(f"\n✅ Credentials are available for SSH container integration")
|
100
|
+
print("💡 These credentials will be automatically passed to SSH containers")
|
101
|
+
else:
|
102
|
+
print(f"\n⚠️ No credentials found")
|
103
|
+
print("💡 Consider setting up credentials using the credentials manager")
|
104
|
+
|
105
|
+
return all_creds
|
106
|
+
|
107
|
+
if __name__ == "__main__":
|
108
|
+
test_credentials_integration()
|
@@ -30,11 +30,9 @@ args, unknown = parser.parse_known_args()
|
|
30
30
|
# Set proxy URL and API key in environment variables if provided
|
31
31
|
if args.proxy_url:
|
32
32
|
os.environ["MODAL_PROXY_URL"] = args.proxy_url
|
33
|
-
# print(f"✅ Set MODAL_PROXY_URL from command line")
|
34
33
|
|
35
34
|
if args.proxy_api_key:
|
36
35
|
os.environ["MODAL_PROXY_API_KEY"] = args.proxy_api_key
|
37
|
-
# print(f"✅ Set MODAL_PROXY_API_KEY from command line")
|
38
36
|
|
39
37
|
class PersistentShell:
|
40
38
|
"""A persistent bash shell using subprocess.Popen for executing commands with state persistence."""
|
@@ -446,26 +444,20 @@ except Exception as e:
|
|
446
444
|
# Apply the comprehensive Modal token solution as fallback
|
447
445
|
try:
|
448
446
|
# Import the comprehensive solution module
|
449
|
-
# print("🔄 Applying comprehensive Modal token solution...")
|
450
447
|
import modal_token_solution
|
451
|
-
# print("✅ Comprehensive Modal token solution applied")
|
452
448
|
|
453
449
|
# Set token variables for later use
|
454
450
|
token = modal_token_solution.TOKEN_ID # For backward compatibility
|
455
451
|
except Exception as e:
|
456
|
-
# print(f"⚠️ Error applying comprehensive Modal token solution: {e}")
|
457
452
|
|
458
453
|
# Fall back to the authentication patch
|
459
454
|
try:
|
460
455
|
# Import the patch module
|
461
|
-
# print("🔄 Falling back to Modal authentication patch...")
|
462
456
|
import modal_auth_patch
|
463
|
-
# print("✅ Modal authentication patch applied")
|
464
457
|
|
465
458
|
# Set token variables for later use
|
466
459
|
token = modal_auth_patch.TOKEN_ID # For backward compatibility
|
467
460
|
except Exception as e:
|
468
|
-
# print(f"⚠️ Error applying Modal authentication patch: {e}")
|
469
461
|
|
470
462
|
# Fall back to fix_modal_token.py
|
471
463
|
try:
|
@@ -500,22 +492,14 @@ except Exception as e:
|
|
500
492
|
token = "ak-sLhYqCjkvixiYcb9LAuCHp" # Default token ID
|
501
493
|
except Exception as e:
|
502
494
|
print(f"⚠️ Error running fix_modal_token.py: {e}")
|
503
|
-
|
504
|
-
# Last resort: use hardcoded tokens
|
505
495
|
token = "ak-sLhYqCjkvixiYcb9LAuCHp" # Default token ID
|
506
496
|
|
507
|
-
# Print debug info
|
508
|
-
# print(f"🔍 DEBUG: Checking environment variables")
|
509
|
-
# print(f"🔍 Token ID exists: {'Yes' if os.environ.get('MODAL_TOKEN_ID') else 'No'}")
|
510
|
-
# print(f"🔍 Token secret exists: {'Yes' if os.environ.get('MODAL_TOKEN_SECRET') else 'No'}")
|
511
|
-
# print(f"🔍 Token exists: {'Yes' if os.environ.get('MODAL_TOKEN') else 'No'}")
|
512
497
|
if os.environ.get('MODAL_TOKEN_ID'):
|
513
498
|
print(f"🔍 Token ID length: {len(os.environ.get('MODAL_TOKEN_ID'))}")
|
514
499
|
if os.environ.get('MODAL_TOKEN_SECRET'):
|
515
500
|
print(f"🔍 Token secret length: {len(os.environ.get('MODAL_TOKEN_SECRET'))}")
|
516
501
|
if os.environ.get('MODAL_TOKEN'):
|
517
502
|
print(f"🔍 Token length: {len(os.environ.get('MODAL_TOKEN'))}")
|
518
|
-
# print(f"✅ Token setup completed")
|
519
503
|
|
520
504
|
# Import modal after token setup
|
521
505
|
import modal
|
@@ -544,168 +528,6 @@ def handle_interactive_input(prompt, is_password=False):
|
|
544
528
|
print(f"❌ Error getting input: {e}")
|
545
529
|
return None
|
546
530
|
|
547
|
-
def handle_wandb_login(sandbox, current_dir):
|
548
|
-
"""Handle Weights & Biases login with proper API key input"""
|
549
|
-
# Define _to_str function locally to avoid NameError
|
550
|
-
def _to_str(maybe_bytes):
|
551
|
-
try:
|
552
|
-
return (maybe_bytes.decode('utf-8') if isinstance(maybe_bytes, (bytes, bytearray)) else maybe_bytes)
|
553
|
-
except UnicodeDecodeError:
|
554
|
-
# Handle non-UTF-8 bytes by replacing invalid characters
|
555
|
-
if isinstance(maybe_bytes, (bytes, bytearray)):
|
556
|
-
return maybe_bytes.decode('utf-8', errors='replace')
|
557
|
-
else:
|
558
|
-
return str(maybe_bytes)
|
559
|
-
except Exception:
|
560
|
-
# Last resort fallback
|
561
|
-
return str(maybe_bytes)
|
562
|
-
|
563
|
-
print("\n🔑 WEIGHTS & BIASES LOGIN")
|
564
|
-
print("="*60)
|
565
|
-
print("Setting up Weights & Biases credentials")
|
566
|
-
print("You can get your API key from: https://wandb.ai/authorize")
|
567
|
-
|
568
|
-
# Try to use credentials manager first
|
569
|
-
api_key = None
|
570
|
-
try:
|
571
|
-
from credentials_manager import CredentialsManager
|
572
|
-
credentials_manager = CredentialsManager()
|
573
|
-
api_key = credentials_manager.get_wandb_api_key()
|
574
|
-
except ImportError:
|
575
|
-
# Fall back to direct input if credentials_manager is not available
|
576
|
-
pass
|
577
|
-
|
578
|
-
# If credentials manager didn't provide a key, use direct input
|
579
|
-
if not api_key:
|
580
|
-
# Get API key from user
|
581
|
-
api_key = handle_interactive_input(
|
582
|
-
"🔑 WEIGHTS & BIASES API KEY REQUIRED\n" +
|
583
|
-
"Please paste your W&B API key below:\n" +
|
584
|
-
"(Your API key should be 40 characters long)",
|
585
|
-
is_password=True
|
586
|
-
)
|
587
|
-
|
588
|
-
if not api_key:
|
589
|
-
print("❌ No API key provided. Cannot continue with W&B login.")
|
590
|
-
return False, "", "No W&B API key provided"
|
591
|
-
|
592
|
-
# Validate API key length
|
593
|
-
if len(api_key) != 40:
|
594
|
-
print(f"⚠️ Warning: API key should be 40 characters long, yours was {len(api_key)}")
|
595
|
-
confirm = handle_interactive_input("Continue anyway? (yes/no)")
|
596
|
-
if not confirm or confirm.lower() not in ["yes", "y"]:
|
597
|
-
print("❌ W&B login cancelled.")
|
598
|
-
return False, "", "W&B login cancelled"
|
599
|
-
|
600
|
-
# Use non-interactive login
|
601
|
-
cmd = f"wandb login {api_key}"
|
602
|
-
print(f"🔄 Running non-interactive login command")
|
603
|
-
|
604
|
-
# Execute the command
|
605
|
-
result = sandbox.exec("bash", "-c", f"cd {current_dir} && {cmd}")
|
606
|
-
|
607
|
-
# Collect output
|
608
|
-
stdout_lines = []
|
609
|
-
stderr_lines = []
|
610
|
-
|
611
|
-
for line in result.stdout:
|
612
|
-
line_str = _to_str(line)
|
613
|
-
stdout_lines.append(line_str)
|
614
|
-
sys.stdout.write(line_str)
|
615
|
-
sys.stdout.flush()
|
616
|
-
|
617
|
-
for line in result.stderr:
|
618
|
-
line_str = _to_str(line)
|
619
|
-
stderr_lines.append(line_str)
|
620
|
-
sys.stderr.write(line_str)
|
621
|
-
sys.stderr.flush()
|
622
|
-
|
623
|
-
result.wait()
|
624
|
-
exit_code = result.returncode
|
625
|
-
|
626
|
-
stdout_buffer = ''.join(stdout_lines)
|
627
|
-
stderr_buffer = ''.join(stderr_lines)
|
628
|
-
|
629
|
-
if exit_code == 0:
|
630
|
-
print("✅ Weights & Biases login successful")
|
631
|
-
# Also set the environment variable for this session
|
632
|
-
os.environ["WANDB_API_KEY"] = api_key
|
633
|
-
print("✅ WANDB_API_KEY environment variable set")
|
634
|
-
else:
|
635
|
-
print(f"❌ Weights & Biases login failed with exit code {exit_code}")
|
636
|
-
if stderr_buffer:
|
637
|
-
print(f"Error: {stderr_buffer}")
|
638
|
-
|
639
|
-
return exit_code == 0, stdout_buffer, stderr_buffer
|
640
|
-
|
641
|
-
def handle_huggingface_login(sandbox, current_dir):
|
642
|
-
"""Handle Hugging Face login with proper token input"""
|
643
|
-
# Define _to_str function locally to avoid NameError
|
644
|
-
def _to_str(maybe_bytes):
|
645
|
-
try:
|
646
|
-
return (maybe_bytes.decode('utf-8') if isinstance(maybe_bytes, (bytes, bytearray)) else maybe_bytes)
|
647
|
-
except UnicodeDecodeError:
|
648
|
-
# Handle non-UTF-8 bytes by replacing invalid characters
|
649
|
-
if isinstance(maybe_bytes, (bytes, bytearray)):
|
650
|
-
return maybe_bytes.decode('utf-8', errors='replace')
|
651
|
-
else:
|
652
|
-
return str(maybe_bytes)
|
653
|
-
except Exception:
|
654
|
-
# Last resort fallback
|
655
|
-
return str(maybe_bytes)
|
656
|
-
|
657
|
-
print("\n🔑 HUGGING FACE LOGIN")
|
658
|
-
print("="*60)
|
659
|
-
print("Setting up Hugging Face credentials")
|
660
|
-
|
661
|
-
# Get token from user
|
662
|
-
token = prompt_for_hf_token()
|
663
|
-
if not token:
|
664
|
-
print("❌ No token provided. Cannot continue with Hugging Face login.")
|
665
|
-
return False, "", "No Hugging Face token provided"
|
666
|
-
|
667
|
-
# Use non-interactive login
|
668
|
-
cmd = f"huggingface-cli login --token {token} --add-to-git-credential"
|
669
|
-
print(f"🔄 Running non-interactive login command")
|
670
|
-
|
671
|
-
# Execute the command
|
672
|
-
result = sandbox.exec("bash", "-c", f"cd {current_dir} && {cmd}")
|
673
|
-
|
674
|
-
# Collect output
|
675
|
-
stdout_lines = []
|
676
|
-
stderr_lines = []
|
677
|
-
|
678
|
-
for line in result.stdout:
|
679
|
-
line_str = _to_str(line)
|
680
|
-
stdout_lines.append(line_str)
|
681
|
-
sys.stdout.write(line_str)
|
682
|
-
sys.stdout.flush()
|
683
|
-
|
684
|
-
for line in result.stderr:
|
685
|
-
line_str = _to_str(line)
|
686
|
-
stderr_lines.append(line_str)
|
687
|
-
sys.stderr.write(line_str)
|
688
|
-
sys.stderr.flush()
|
689
|
-
|
690
|
-
result.wait()
|
691
|
-
exit_code = result.returncode
|
692
|
-
|
693
|
-
stdout_buffer = ''.join(stdout_lines)
|
694
|
-
stderr_buffer = ''.join(stderr_lines)
|
695
|
-
|
696
|
-
if exit_code == 0:
|
697
|
-
print("✅ Hugging Face login successful")
|
698
|
-
# Also set the environment variable for this session
|
699
|
-
os.environ["HF_TOKEN"] = token
|
700
|
-
print("✅ HF_TOKEN environment variable set")
|
701
|
-
else:
|
702
|
-
print(f"❌ Hugging Face login failed with exit code {exit_code}")
|
703
|
-
if stderr_buffer:
|
704
|
-
print(f"Error: {stderr_buffer}")
|
705
|
-
|
706
|
-
return exit_code == 0, stdout_buffer, stderr_buffer
|
707
|
-
|
708
|
-
|
709
531
|
def get_stored_credentials():
|
710
532
|
"""Load stored credentials from ~/.gitarsenal/credentials.json"""
|
711
533
|
import json
|
@@ -788,8 +610,6 @@ def call_openai_for_debug(command, error_output, api_key=None, current_dir=None,
|
|
788
610
|
from fetch_modal_tokens import get_tokens
|
789
611
|
_, _, api_key = get_tokens()
|
790
612
|
if api_key:
|
791
|
-
# print("✅ Successfully fetched OpenAI API key from server")
|
792
|
-
# print(f"🔍 DEBUG: Fetched OpenAI API key value: {api_key}")
|
793
613
|
# Set in environment for this session
|
794
614
|
os.environ["OPENAI_API_KEY"] = api_key
|
795
615
|
else:
|
@@ -930,29 +750,25 @@ System Information:
|
|
930
750
|
print("🔍 Getting directory context for better debugging...")
|
931
751
|
|
932
752
|
# Get current directory contents
|
933
|
-
ls_result = sandbox.exec("bash", "-c",
|
753
|
+
ls_result = sandbox.exec("bash", "-c", "ls -la")
|
934
754
|
ls_output = ""
|
935
755
|
for line in ls_result.stdout:
|
936
756
|
ls_output += _to_str(line)
|
937
757
|
ls_result.wait()
|
938
758
|
|
939
|
-
# Get parent directory contents
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
parent_ls += _to_str(line)
|
947
|
-
parent_result.wait()
|
948
|
-
parent_context = f"\nParent directory ({parent_dir}) contents:\n{parent_ls}"
|
949
|
-
|
759
|
+
# Get parent directory contents
|
760
|
+
parent_result = sandbox.exec("bash", "-c", "ls -la ../")
|
761
|
+
parent_ls = ""
|
762
|
+
for line in parent_result.stdout:
|
763
|
+
parent_ls += _to_str(line)
|
764
|
+
parent_result.wait()
|
765
|
+
|
950
766
|
directory_context = f"""
|
951
|
-
Current directory:
|
952
|
-
|
953
|
-
Directory contents:
|
767
|
+
Current directory contents:
|
954
768
|
{ls_output}
|
955
|
-
|
769
|
+
|
770
|
+
Parent directory contents:
|
771
|
+
{parent_ls}
|
956
772
|
"""
|
957
773
|
print("✅ Directory context gathered successfully")
|
958
774
|
|
@@ -966,11 +782,11 @@ Directory contents:
|
|
966
782
|
file_path = file_path.strip("'\"")
|
967
783
|
if not os.path.isabs(file_path):
|
968
784
|
file_path = os.path.join(current_dir, file_path)
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
785
|
+
|
786
|
+
# Try to get the parent directory if the file doesn't exist
|
787
|
+
if '/' in file_path:
|
788
|
+
parent_file_dir = os.path.dirname(file_path)
|
789
|
+
relevant_files.append(parent_file_dir)
|
974
790
|
|
975
791
|
# Look for package.json, requirements.txt, etc.
|
976
792
|
common_config_files = ["package.json", "requirements.txt", "pyproject.toml", "setup.py",
|
@@ -1033,6 +849,12 @@ Directory contents:
|
|
1033
849
|
|
1034
850
|
stored_credentials = get_stored_credentials()
|
1035
851
|
auth_context = generate_auth_context(stored_credentials)
|
852
|
+
|
853
|
+
|
854
|
+
print("DEBUG: AUTH_CONTEXT SENT TO LLM:")
|
855
|
+
print("="*60)
|
856
|
+
print(auth_context)
|
857
|
+
print("="*60 + "\n")
|
1036
858
|
|
1037
859
|
# Create a prompt for the LLM
|
1038
860
|
print("\n" + "="*60)
|
@@ -1081,17 +903,25 @@ IMPORTANT GUIDELINES:
|
|
1081
903
|
|
1082
904
|
4. For authentication issues:
|
1083
905
|
- Analyze the error to determine what type of authentication is needed
|
1084
|
-
-
|
906
|
+
- ALWAYS use the actual credential values from the AVAILABLE CREDENTIALS section above (NOT placeholders)
|
907
|
+
- Look for the specific API key or token needed in the auth_context and use its exact value
|
1085
908
|
- Common patterns:
|
1086
|
-
* wandb errors: use wandb login with WANDB_API_KEY
|
1087
|
-
* huggingface errors: use huggingface-cli login with HF_TOKEN or HUGGINGFACE_TOKEN
|
1088
|
-
* github errors: configure git credentials with GITHUB_TOKEN
|
1089
|
-
* kaggle errors: create ~/.kaggle/kaggle.json with KAGGLE_USERNAME and KAGGLE_KEY
|
1090
|
-
* API errors: export the appropriate API key as environment variable
|
909
|
+
* wandb errors: use wandb login with the actual WANDB_API_KEY value from auth_context
|
910
|
+
* huggingface errors: use huggingface-cli login with the actual HF_TOKEN or HUGGINGFACE_TOKEN value from auth_context
|
911
|
+
* github errors: configure git credentials with the actual GITHUB_TOKEN value from auth_context
|
912
|
+
* kaggle errors: create ~/.kaggle/kaggle.json with the actual KAGGLE_USERNAME and KAGGLE_KEY values from auth_context
|
913
|
+
* API errors: export the appropriate API key as environment variable using the actual value from auth_context
|
1091
914
|
|
1092
915
|
5. Environment variable exports:
|
1093
916
|
- Use export commands for API keys that need to be in environment
|
1094
|
-
-
|
917
|
+
- ALWAYS use the actual credential values from auth_context, never use placeholders like "YOUR_API_KEY"
|
918
|
+
- Example: export OPENAI_API_KEY="sk-..." (using the actual key from auth_context)
|
919
|
+
|
920
|
+
6. CRITICAL: When using any API key, token, or credential:
|
921
|
+
- Find the exact value in the AVAILABLE CREDENTIALS section
|
922
|
+
- Use that exact value in your command
|
923
|
+
- Do not use generic placeholders or dummy values
|
924
|
+
- The auth_context contains real, usable credentials
|
1095
925
|
|
1096
926
|
Do not provide any explanations, just the exact command to run.
|
1097
927
|
"""
|
@@ -1362,31 +1192,6 @@ def prompt_for_hf_token():
|
|
1362
1192
|
print(f"❌ Error getting token: {e}")
|
1363
1193
|
return None
|
1364
1194
|
|
1365
|
-
|
1366
|
-
def handle_interactive_input(prompt, is_password=False):
|
1367
|
-
"""Handle interactive input from the user with optional password masking"""
|
1368
|
-
print("\n" + "="*60)
|
1369
|
-
print(f"{prompt}")
|
1370
|
-
print("="*60)
|
1371
|
-
|
1372
|
-
try:
|
1373
|
-
if is_password:
|
1374
|
-
user_input = getpass.getpass("Input (hidden): ").strip()
|
1375
|
-
else:
|
1376
|
-
user_input = input("Input: ").strip()
|
1377
|
-
|
1378
|
-
if not user_input:
|
1379
|
-
print("❌ No input provided.")
|
1380
|
-
return None
|
1381
|
-
print("✅ Input received successfully!")
|
1382
|
-
return user_input
|
1383
|
-
except KeyboardInterrupt:
|
1384
|
-
print("\n❌ Input cancelled by user.")
|
1385
|
-
return None
|
1386
|
-
except Exception as e:
|
1387
|
-
print(f"❌ Error getting input: {e}")
|
1388
|
-
return None
|
1389
|
-
|
1390
1195
|
def generate_random_password(length=16):
|
1391
1196
|
"""Generate a random password for SSH access"""
|
1392
1197
|
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
|
@@ -1618,7 +1423,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1618
1423
|
"python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
|
1619
1424
|
"gpg", "ca-certificates", "software-properties-common"
|
1620
1425
|
)
|
1621
|
-
.
|
1426
|
+
.pip_install("uv", "modal", "requests", "openai") # Remove problematic CUDA packages
|
1622
1427
|
.run_commands(
|
1623
1428
|
# Create SSH directory
|
1624
1429
|
"mkdir -p /var/run/sshd",
|
@@ -1668,11 +1473,12 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1668
1473
|
serialized=True,
|
1669
1474
|
volumes=volumes_config if volumes_config else None,
|
1670
1475
|
)
|
1671
|
-
def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None):
|
1476
|
+
def ssh_container_function(ssh_password=None, repo_url=None, repo_name=None, setup_commands=None, openai_api_key=None, stored_credentials=None):
|
1672
1477
|
"""Start SSH container with password authentication and optional setup."""
|
1673
1478
|
import subprocess
|
1674
1479
|
import time
|
1675
1480
|
import os
|
1481
|
+
import json
|
1676
1482
|
|
1677
1483
|
# Set root password
|
1678
1484
|
subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
|
@@ -1680,37 +1486,52 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1680
1486
|
# Set OpenAI API key if provided
|
1681
1487
|
if openai_api_key:
|
1682
1488
|
os.environ['OPENAI_API_KEY'] = openai_api_key
|
1683
|
-
print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
|
1489
|
+
# print(f"✅ Set OpenAI API key in container environment (length: {len(openai_api_key)})")
|
1684
1490
|
else:
|
1685
1491
|
print("⚠️ No OpenAI API key provided to container")
|
1686
1492
|
|
1687
|
-
#
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1493
|
+
# Set up stored credentials in container environment
|
1494
|
+
if stored_credentials:
|
1495
|
+
print(f"🔐 Setting up {len(stored_credentials)} stored credentials in container...")
|
1496
|
+
for key, value in stored_credentials.items():
|
1497
|
+
# Set each credential as an environment variable
|
1498
|
+
env_var_name = key.upper().replace('-', '_').replace(' ', '_')
|
1499
|
+
os.environ[env_var_name] = value
|
1500
|
+
print(f"✅ Set {env_var_name} in container environment")
|
1695
1501
|
|
1502
|
+
# Also save credentials to a file in the container for easy access
|
1696
1503
|
try:
|
1697
|
-
|
1698
|
-
|
1504
|
+
credentials_dir = "/root/.gitarsenal"
|
1505
|
+
os.makedirs(credentials_dir, exist_ok=True)
|
1506
|
+
credentials_file = os.path.join(credentials_dir, "credentials.json")
|
1507
|
+
with open(credentials_file, 'w') as f:
|
1508
|
+
json.dump(stored_credentials, f, indent=2)
|
1509
|
+
print(f"✅ Saved credentials to {credentials_file}")
|
1699
1510
|
|
1700
|
-
#
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1511
|
+
# Print available credentials for user reference
|
1512
|
+
print("\n🔐 AVAILABLE CREDENTIALS IN CONTAINER:")
|
1513
|
+
print("="*50)
|
1514
|
+
for key, value in stored_credentials.items():
|
1515
|
+
masked_value = value[:8] + "..." if len(value) > 8 else "***"
|
1516
|
+
env_var_name = key.upper().replace('-', '_').replace(' ', '_')
|
1517
|
+
print(f" {key} -> {env_var_name} = {masked_value}")
|
1518
|
+
print("="*50)
|
1519
|
+
print("💡 These credentials are available as environment variables and in /root/.gitarsenal/credentials.json")
|
1704
1520
|
|
1705
|
-
except
|
1706
|
-
print(f"
|
1521
|
+
except Exception as e:
|
1522
|
+
print(f"⚠️ Could not save credentials file: {e}")
|
1523
|
+
else:
|
1524
|
+
print("⚠️ No stored credentials provided to container")
|
1525
|
+
|
1526
|
+
# Start SSH service
|
1527
|
+
subprocess.run(["service", "ssh", "start"], check=True)
|
1707
1528
|
|
1708
1529
|
# Run setup commands if provided using PersistentShell
|
1709
1530
|
if setup_commands:
|
1710
1531
|
print(f"⚙️ Running {len(setup_commands)} setup commands with persistent shell...")
|
1711
1532
|
|
1712
|
-
# Create persistent shell instance
|
1713
|
-
shell = PersistentShell(working_dir=
|
1533
|
+
# Create persistent shell instance starting in /root
|
1534
|
+
shell = PersistentShell(working_dir="/root", timeout=120)
|
1714
1535
|
|
1715
1536
|
try:
|
1716
1537
|
# Start the persistent shell
|
@@ -1808,7 +1629,15 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
|
|
1808
1629
|
with app.run():
|
1809
1630
|
# Get the API key from environment
|
1810
1631
|
api_key = os.environ.get("OPENAI_API_KEY")
|
1811
|
-
|
1632
|
+
|
1633
|
+
# Get stored credentials from local file
|
1634
|
+
stored_credentials = get_stored_credentials()
|
1635
|
+
if stored_credentials:
|
1636
|
+
print(f"🔐 Found {len(stored_credentials)} stored credentials to send to container")
|
1637
|
+
else:
|
1638
|
+
print("⚠️ No stored credentials found")
|
1639
|
+
|
1640
|
+
ssh_container_function.remote(ssh_password, repo_url, repo_name, setup_commands, api_key, stored_credentials)
|
1812
1641
|
|
1813
1642
|
# Clean up Modal token after container is successfully created
|
1814
1643
|
cleanup_modal_token()
|