gitarsenal-cli 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1841,7 +1841,109 @@ def generate_random_password(length=16):
1841
1841
  password = ''.join(secrets.choice(alphabet) for i in range(length))
1842
1842
  return password
1843
1843
 
1844
+ # First, add the standalone ssh_container function at the module level, before the create_modal_ssh_container function
1844
1845
 
1846
+ # Define a module-level ssh container function
1847
+ ssh_app = modal.App("ssh-container-app")
1848
+
1849
+ @ssh_app.function(
1850
+ image=modal.Image.debian_slim()
1851
+ .apt_install(
1852
+ "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
1853
+ "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
1854
+ "gpg", "ca-certificates", "software-properties-common"
1855
+ )
1856
+ .pip_install("uv")
1857
+ .run_commands(
1858
+ # Create SSH directory
1859
+ "mkdir -p /var/run/sshd",
1860
+ "mkdir -p /root/.ssh",
1861
+ "chmod 700 /root/.ssh",
1862
+
1863
+ # Configure SSH server
1864
+ "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
1865
+ "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
1866
+ "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
1867
+
1868
+ # SSH keep-alive settings
1869
+ "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
1870
+ "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
1871
+
1872
+ # Generate SSH host keys
1873
+ "ssh-keygen -A",
1874
+
1875
+ # Set up a nice bash prompt
1876
+ "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
1877
+ ),
1878
+ timeout=3600, # Default 1 hour timeout
1879
+ gpu="a10g", # Default GPU
1880
+ cpu=2,
1881
+ memory=8192,
1882
+ serialized=True,
1883
+ )
1884
+ def ssh_container_function(ssh_password, repo_url=None, repo_name=None, setup_commands=None):
1885
+ import subprocess
1886
+ import time
1887
+ import os
1888
+
1889
+ # Set root password
1890
+ subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
1891
+
1892
+ # Start SSH service
1893
+ subprocess.run(["service", "ssh", "start"], check=True)
1894
+
1895
+ # Setup environment
1896
+ os.environ['PS1'] = r'\[\e[1;32m\]modal:\[\e[1;34m\]\w\[\e[0m\]$ '
1897
+
1898
+ # Clone repository if provided
1899
+ if repo_url:
1900
+ repo_name_from_url = repo_name or repo_url.split('/')[-1].replace('.git', '')
1901
+ print(f"šŸ“„ Cloning repository: {repo_url}")
1902
+
1903
+ try:
1904
+ subprocess.run(["git", "clone", repo_url], check=True, cwd="/root")
1905
+ print(f"āœ… Repository cloned successfully: {repo_name_from_url}")
1906
+
1907
+ # Change to repository directory
1908
+ repo_dir = f"/root/{repo_name_from_url}"
1909
+ if os.path.exists(repo_dir):
1910
+ os.chdir(repo_dir)
1911
+ print(f"šŸ“‚ Changed to repository directory: {repo_dir}")
1912
+
1913
+ except subprocess.CalledProcessError as e:
1914
+ print(f"āŒ Failed to clone repository: {e}")
1915
+
1916
+ # Run setup commands if provided
1917
+ if setup_commands:
1918
+ print(f"āš™ļø Running {len(setup_commands)} setup commands...")
1919
+ for i, cmd in enumerate(setup_commands, 1):
1920
+ print(f"šŸ“‹ Executing command {i}/{len(setup_commands)}: {cmd}")
1921
+ try:
1922
+ result = subprocess.run(cmd, shell=True, check=True,
1923
+ capture_output=True, text=True)
1924
+ if result.stdout:
1925
+ print(f"āœ… Output: {result.stdout}")
1926
+ except subprocess.CalledProcessError as e:
1927
+ print(f"āŒ Command failed: {e}")
1928
+ if e.stderr:
1929
+ print(f"āŒ Error: {e.stderr}")
1930
+
1931
+ # Get container info
1932
+ print("šŸ” Container started successfully!")
1933
+ print(f"šŸ†” Container ID: {os.environ.get('MODAL_TASK_ID', 'unknown')}")
1934
+
1935
+ # Keep the container running
1936
+ while True:
1937
+ time.sleep(30)
1938
+ # Check if SSH service is still running
1939
+ try:
1940
+ subprocess.run(["service", "ssh", "status"], check=True,
1941
+ capture_output=True)
1942
+ except subprocess.CalledProcessError:
1943
+ print("āš ļø SSH service stopped, restarting...")
1944
+ subprocess.run(["service", "ssh", "start"], check=True)
1945
+
1946
+ # Now modify the create_modal_ssh_container function to use the standalone ssh_container_function
1845
1947
 
1846
1948
  def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_commands=None,
1847
1949
  volume_name=None, timeout_minutes=60, ssh_password=None):
@@ -1897,125 +1999,25 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
1897
1999
  print("āš ļø Continuing without persistent volume")
1898
2000
  volume = None
1899
2001
 
1900
- # Create SSH-enabled image
1901
- ssh_image = (
1902
- modal.Image.debian_slim()
1903
- .apt_install(
1904
- "openssh-server", "sudo", "curl", "wget", "vim", "htop", "git",
1905
- "python3", "python3-pip", "build-essential", "tmux", "screen", "nano",
1906
- "gpg", "ca-certificates", "software-properties-common"
1907
- )
1908
- .pip_install("uv") # Fast Python package installer
1909
- .run_commands(
1910
- # Create SSH directory
1911
- "mkdir -p /var/run/sshd",
1912
- "mkdir -p /root/.ssh",
1913
- "chmod 700 /root/.ssh",
1914
-
1915
- # Configure SSH server
1916
- "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config",
1917
- "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config",
1918
- "sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config",
1919
-
1920
- # SSH keep-alive settings
1921
- "echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config",
1922
- "echo 'ClientAliveCountMax 3' >> /etc/ssh/sshd_config",
1923
-
1924
- # Generate SSH host keys
1925
- "ssh-keygen -A",
1926
-
1927
- # Set up a nice bash prompt
1928
- "echo 'export PS1=\"\\[\\e[1;32m\\]modal:\\[\\e[1;34m\\]\\w\\[\\e[0m\\]$ \"' >> /root/.bashrc",
1929
- )
1930
- )
1931
-
1932
2002
  # Create Modal app
1933
2003
  with modal.enable_output():
1934
2004
  print(f"šŸš€ Creating Modal app: {app_name}")
1935
- app = modal.App.lookup(app_name, create_if_missing=True)
1936
2005
 
1937
- # Setup volume mount if available
1938
- volumes = {}
1939
- if volume:
1940
- volumes[volume_mount_path] = volume
1941
- print(f"šŸ“¦ Mounting volume '{volume_name}' at {volume_mount_path}")
2006
+ # Configure the global ssh_container_function with the correct parameters
2007
+ global ssh_container_function
1942
2008
 
1943
- @app.function(
1944
- image=ssh_image,
1945
- timeout=timeout_minutes * 60, # Convert to seconds
2009
+ # Update the function's configuration with our specific needs
2010
+ ssh_container_function = ssh_container_function.update(
1946
2011
  gpu=gpu_spec['gpu'],
1947
- cpu=2,
1948
- memory=8192,
1949
- serialized=True,
1950
- volumes=volumes if volumes else None,
2012
+ timeout=timeout_minutes * 60,
2013
+ volumes={volume_mount_path: volume} if volume else None
1951
2014
  )
1952
- def ssh_container():
1953
- import subprocess
1954
- import time
1955
- import os
1956
-
1957
- # Set root password
1958
- subprocess.run(["bash", "-c", f"echo 'root:{ssh_password}' | chpasswd"], check=True)
1959
-
1960
- # Start SSH service
1961
- subprocess.run(["service", "ssh", "start"], check=True)
1962
-
1963
- # Setup environment
1964
- os.environ['PS1'] = r'\[\e[1;32m\]modal:\[\e[1;34m\]\w\[\e[0m\]$ '
1965
-
1966
- # Clone repository if provided
1967
- if repo_url:
1968
- repo_name_from_url = repo_name or repo_url.split('/')[-1].replace('.git', '')
1969
- print(f"šŸ“„ Cloning repository: {repo_url}")
1970
-
1971
- try:
1972
- subprocess.run(["git", "clone", repo_url], check=True, cwd="/root")
1973
- print(f"āœ… Repository cloned successfully: {repo_name_from_url}")
1974
-
1975
- # Change to repository directory
1976
- repo_dir = f"/root/{repo_name_from_url}"
1977
- if os.path.exists(repo_dir):
1978
- os.chdir(repo_dir)
1979
- print(f"šŸ“‚ Changed to repository directory: {repo_dir}")
1980
-
1981
- except subprocess.CalledProcessError as e:
1982
- print(f"āŒ Failed to clone repository: {e}")
1983
-
1984
- # Run setup commands if provided
1985
- if setup_commands:
1986
- print(f"āš™ļø Running {len(setup_commands)} setup commands...")
1987
- for i, cmd in enumerate(setup_commands, 1):
1988
- print(f"šŸ“‹ Executing command {i}/{len(setup_commands)}: {cmd}")
1989
- try:
1990
- result = subprocess.run(cmd, shell=True, check=True,
1991
- capture_output=True, text=True)
1992
- if result.stdout:
1993
- print(f"āœ… Output: {result.stdout}")
1994
- except subprocess.CalledProcessError as e:
1995
- print(f"āŒ Command failed: {e}")
1996
- if e.stderr:
1997
- print(f"āŒ Error: {e.stderr}")
1998
-
1999
- # Get container info
2000
- print("šŸ” Container started successfully!")
2001
- print(f"šŸ†” Container ID: {os.environ.get('MODAL_TASK_ID', 'unknown')}")
2002
-
2003
- # Keep the container running
2004
- while True:
2005
- time.sleep(30)
2006
- # Check if SSH service is still running
2007
- try:
2008
- subprocess.run(["service", "ssh", "status"], check=True,
2009
- capture_output=True)
2010
- except subprocess.CalledProcessError:
2011
- print("āš ļø SSH service stopped, restarting...")
2012
- subprocess.run(["service", "ssh", "start"], check=True)
2013
2015
 
2014
2016
  # Start the container
2015
2017
  print("šŸš€ Starting SSH container...")
2016
2018
 
2017
2019
  # Use spawn to run the container in the background
2018
- container_handle = ssh_container.spawn()
2020
+ container_handle = ssh_container_function.spawn(ssh_password, repo_url, repo_name, setup_commands)
2019
2021
 
2020
2022
  # Wait a moment for the container to start
2021
2023
  print("ā³ Waiting for container to initialize...")
@@ -2037,7 +2039,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2037
2039
  if containers and isinstance(containers, list):
2038
2040
  # Find the most recent container
2039
2041
  for container in containers:
2040
- if container.get("App") == app_name:
2042
+ if container.get("App") == "ssh-container-app":
2041
2043
  container_id = container.get("Container ID")
2042
2044
  break
2043
2045
 
@@ -2055,7 +2057,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2055
2057
  if result.returncode == 0:
2056
2058
  lines = result.stdout.split('\n')
2057
2059
  for line in lines:
2058
- if app_name in line or ('ta-' in line and '│' in line):
2060
+ if "ssh-container-app" in line or ('ta-' in line and '│' in line):
2059
2061
  parts = line.split('│')
2060
2062
  if len(parts) >= 2:
2061
2063
  possible_id = parts[1].strip()
@@ -2079,7 +2081,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2079
2081
  print("="*80)
2080
2082
  print(f"šŸ†” Container ID: {container_id}")
2081
2083
  print(f"šŸ” SSH Password: {ssh_password}")
2082
- print(f"šŸ“± App Name: {app_name}")
2084
+ print(f"šŸ“± App Name: ssh-container-app")
2083
2085
  if volume:
2084
2086
  print(f"šŸ’¾ Volume: {volume_name} (mounted at {volume_mount_path})")
2085
2087
  print("\nšŸ”— SSH Connection:")
@@ -2122,7 +2124,7 @@ def create_modal_ssh_container(gpu_type, repo_url=None, repo_name=None, setup_co
2122
2124
  return {
2123
2125
  "container_handle": container_handle,
2124
2126
  "container_id": container_id,
2125
- "app_name": app_name,
2127
+ "app_name": "ssh-container-app",
2126
2128
  "ssh_password": ssh_password,
2127
2129
  "volume_name": volume_name,
2128
2130
  "volume_mount_path": volume_mount_path if volume else None
@@ -2687,7 +2689,7 @@ if __name__ == "__main__":
2687
2689
  # Try alternative approach with iTerm2
2688
2690
  try:
2689
2691
  iterm_script = f'''
2690
- tell application "iTerm"
2692
+ tell application "iTerm"
2691
2693
  create window with default profile
2692
2694
  tell current session of current window
2693
2695
  write text "echo 'Connecting to Modal SSH container...'; echo 'Password: {ssh_password or 'unknown'}'; ssh root@{container_id}.modal.run"