devs-common 2.0.2__tar.gz → 2.0.4__tar.gz
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.
- {devs_common-2.0.2 → devs_common-2.0.4}/PKG-INFO +1 -1
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/core/container.py +139 -26
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/Dockerfile +2 -2
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/devcontainer.json +2 -1
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common.egg-info/PKG-INFO +1 -1
- {devs_common-2.0.2 → devs_common-2.0.4}/pyproject.toml +1 -1
- {devs_common-2.0.2 → devs_common-2.0.4}/LICENSE +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/README.md +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/__init__.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/config.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/core/__init__.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/core/project.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/core/workspace.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/devs_config.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/exceptions.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/scripts/copy-ssh.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/scripts/post-create-wrapper.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/scripts/python-venv-integration.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/scripts/screenshot-webapp.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/scripts/setup-workspace.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/scripts/start-tunnel.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/scripts/tunnel-status.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/scripts/webapp-screenshot.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/sudo-scripts/init-firewall.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/sudo-scripts/setup-devs-env.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/sudo-scripts/setup-vscode-cache.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/sudo-scripts/start-services.sh +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/vscode-settings-reference.md +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/utils/__init__.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/utils/config_hash.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/utils/console.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/utils/devcontainer.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/utils/devcontainer_template.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/utils/docker_client.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/utils/file_utils.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common/utils/git_utils.py +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common.egg-info/SOURCES.txt +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common.egg-info/dependency_links.txt +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common.egg-info/requires.txt +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/devs_common.egg-info/top_level.txt +0 -0
- {devs_common-2.0.2 → devs_common-2.0.4}/setup.cfg +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
+
import time
|
|
5
6
|
from datetime import datetime, timezone
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Dict, List, Optional, Tuple
|
|
@@ -817,14 +818,34 @@ class ContainerManager:
|
|
|
817
818
|
extra_env=extra_env
|
|
818
819
|
)
|
|
819
820
|
|
|
820
|
-
def
|
|
821
|
-
"""
|
|
821
|
+
def _get_tunnel_name(self, dev_name: str) -> str:
|
|
822
|
+
"""Generate a VS Code tunnel name, truncated to 20 chars.
|
|
822
823
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
824
|
+
VS Code tunnel names have a 20 character limit. We keep the dev name
|
|
825
|
+
suffix and shorten the project portion if needed.
|
|
826
|
+
"""
|
|
827
|
+
project_prefix = self.config.project_prefix if self.config else "dev"
|
|
828
|
+
tunnel_name = f"{project_prefix}-{self.project.info.name}-{dev_name}"
|
|
829
|
+
# Sanitize: tunnel names can only contain alphanumeric, dash, underscore
|
|
830
|
+
tunnel_name = tunnel_name.replace(".", "-").replace("_", "-")
|
|
831
|
+
|
|
832
|
+
if len(tunnel_name) > 20:
|
|
833
|
+
suffix = f"-{dev_name}"
|
|
834
|
+
prefix_budget = 20 - len(suffix)
|
|
835
|
+
if prefix_budget >= 3:
|
|
836
|
+
project_part = f"{project_prefix}-{self.project.info.name}"
|
|
837
|
+
project_part = project_part[:prefix_budget].rstrip("-")
|
|
838
|
+
tunnel_name = f"{project_part}{suffix}"
|
|
839
|
+
else:
|
|
840
|
+
tunnel_name = tunnel_name[:20]
|
|
826
841
|
|
|
827
|
-
|
|
842
|
+
return tunnel_name
|
|
843
|
+
|
|
844
|
+
def tunnel_auth(self, dev_name: str, workspace_dir: Path, debug: bool = False, live: bool = False, extra_env: Optional[Dict[str, str]] = None) -> None:
|
|
845
|
+
"""Authenticate VS Code tunnel (interactive, one-time setup).
|
|
846
|
+
|
|
847
|
+
Runs the device code flow interactively. Auth is stored in a shared
|
|
848
|
+
Docker volume so it persists across containers and recreations.
|
|
828
849
|
|
|
829
850
|
Args:
|
|
830
851
|
dev_name: Development environment name
|
|
@@ -834,48 +855,140 @@ class ContainerManager:
|
|
|
834
855
|
extra_env: Additional environment variables to pass to container
|
|
835
856
|
|
|
836
857
|
Raises:
|
|
837
|
-
ContainerError: If
|
|
858
|
+
ContainerError: If authentication fails
|
|
838
859
|
"""
|
|
839
860
|
try:
|
|
840
|
-
# Prepare container for execution
|
|
841
861
|
container_name, container_workspace_dir = self._prepare_container_exec(
|
|
842
862
|
dev_name, workspace_dir, debug=debug, live=live, extra_env=extra_env
|
|
843
863
|
)
|
|
844
864
|
|
|
845
|
-
|
|
846
|
-
# This will be the name that appears in VS Code Remote Explorer
|
|
847
|
-
project_prefix = self.config.project_prefix if self.config else "dev"
|
|
848
|
-
tunnel_name = f"{project_prefix}-{self.project.info.name}-{dev_name}"
|
|
849
|
-
# Sanitize: tunnel names can only contain alphanumeric, dash, underscore
|
|
850
|
-
tunnel_name = tunnel_name.replace(".", "-").replace("_", "-")
|
|
865
|
+
tunnel_name = self._get_tunnel_name(dev_name)
|
|
851
866
|
|
|
852
|
-
console.print(
|
|
867
|
+
console.print("[bold cyan]Setting up VS Code tunnel authentication...[/bold cyan]")
|
|
853
868
|
console.print(f" Container: {container_name}")
|
|
854
|
-
console.print(f" Tunnel name: {tunnel_name}")
|
|
855
869
|
console.print("")
|
|
856
|
-
console.print("[yellow]
|
|
870
|
+
console.print("[yellow]Follow the prompts to authenticate:[/yellow]")
|
|
857
871
|
console.print(" 1. A URL and code will be displayed")
|
|
858
872
|
console.print(" 2. Open the URL in your browser")
|
|
859
873
|
console.print(" 3. Enter the code to authenticate with GitHub/Microsoft")
|
|
860
874
|
console.print("")
|
|
861
|
-
console.print("[dim]Press Ctrl+C to stop the tunnel[/dim]")
|
|
862
|
-
console.print("")
|
|
863
875
|
|
|
864
|
-
# Start the tunnel interactively
|
|
865
|
-
# The code CLI binary is installed at /usr/local/bin/code in the container
|
|
866
876
|
cmd = [
|
|
867
877
|
'docker', 'exec', '-it',
|
|
868
878
|
container_name,
|
|
869
|
-
'/usr/local/bin/code', 'tunnel',
|
|
870
|
-
'--
|
|
871
|
-
|
|
879
|
+
'/usr/local/bin/code', 'tunnel', 'user', 'login',
|
|
880
|
+
'--provider', 'github'
|
|
881
|
+
]
|
|
882
|
+
|
|
883
|
+
if debug:
|
|
884
|
+
console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
|
|
885
|
+
|
|
886
|
+
result = subprocess.run(cmd, check=False)
|
|
887
|
+
|
|
888
|
+
if result.returncode == 0:
|
|
889
|
+
console.print("")
|
|
890
|
+
console.print("[bold green]Authentication successful![/bold green]")
|
|
891
|
+
console.print(" Auth is shared across all containers on this machine.")
|
|
892
|
+
console.print("")
|
|
893
|
+
console.print(f" Start a tunnel: devs tunnel {dev_name}")
|
|
894
|
+
console.print(f" Then connect: code --remote tunnel+{tunnel_name} {container_workspace_dir}")
|
|
895
|
+
else:
|
|
896
|
+
console.print("")
|
|
897
|
+
console.print("[yellow]Authentication was cancelled or failed.[/yellow]")
|
|
898
|
+
|
|
899
|
+
except (DockerError, subprocess.SubprocessError) as e:
|
|
900
|
+
raise ContainerError(f"Failed to authenticate tunnel in {dev_name}: {e}")
|
|
901
|
+
|
|
902
|
+
def start_tunnel(self, dev_name: str, workspace_dir: Path, debug: bool = False, live: bool = False, extra_env: Optional[Dict[str, str]] = None) -> None:
|
|
903
|
+
"""Start a VS Code tunnel in the background.
|
|
904
|
+
|
|
905
|
+
Requires prior authentication via tunnel_auth(). The tunnel runs
|
|
906
|
+
detached inside the container.
|
|
907
|
+
|
|
908
|
+
Args:
|
|
909
|
+
dev_name: Development environment name
|
|
910
|
+
workspace_dir: Workspace directory path
|
|
911
|
+
debug: Show debug output for devcontainer operations
|
|
912
|
+
live: Whether the container is in live mode
|
|
913
|
+
extra_env: Additional environment variables to pass to container
|
|
914
|
+
|
|
915
|
+
Raises:
|
|
916
|
+
ContainerError: If tunnel startup fails
|
|
917
|
+
"""
|
|
918
|
+
try:
|
|
919
|
+
container_name, container_workspace_dir = self._prepare_container_exec(
|
|
920
|
+
dev_name, workspace_dir, debug=debug, live=live, extra_env=extra_env
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
tunnel_name = self._get_tunnel_name(dev_name)
|
|
924
|
+
log_file = '/tmp/vscode-tunnel.log'
|
|
925
|
+
tunnel_cmd = f'/usr/local/bin/code tunnel --accept-server-license-terms --name {tunnel_name}'
|
|
926
|
+
vscode_cmd = f"code --remote tunnel+{tunnel_name} {container_workspace_dir}"
|
|
927
|
+
|
|
928
|
+
console.print(f"[bold cyan]Starting VS Code tunnel for: {dev_name}[/bold cyan]")
|
|
929
|
+
console.print(f" Container: {container_name}")
|
|
930
|
+
console.print(f" Tunnel name: {tunnel_name}")
|
|
931
|
+
console.print("")
|
|
932
|
+
|
|
933
|
+
# Start detached
|
|
934
|
+
cmd = [
|
|
935
|
+
'docker', 'exec', '-d',
|
|
936
|
+
container_name,
|
|
937
|
+
'/bin/sh', '-c',
|
|
938
|
+
f'{tunnel_cmd} > {log_file} 2>&1'
|
|
872
939
|
]
|
|
873
940
|
|
|
874
941
|
if debug:
|
|
875
942
|
console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
|
|
876
943
|
|
|
877
|
-
|
|
878
|
-
|
|
944
|
+
subprocess.run(cmd, check=True)
|
|
945
|
+
|
|
946
|
+
# Poll the log to confirm startup and show the URL
|
|
947
|
+
console.print("[dim]Waiting for tunnel to start...[/dim]")
|
|
948
|
+
for i in range(15):
|
|
949
|
+
time.sleep(1)
|
|
950
|
+
result = subprocess.run(
|
|
951
|
+
['docker', 'exec', container_name, 'cat', log_file],
|
|
952
|
+
capture_output=True, text=True
|
|
953
|
+
)
|
|
954
|
+
output = result.stdout
|
|
955
|
+
|
|
956
|
+
if 'Open this link' in output or 'vscode.dev/tunnel' in output:
|
|
957
|
+
console.print(f"[bold green]Tunnel running![/bold green]")
|
|
958
|
+
console.print("")
|
|
959
|
+
console.print("[bold]Connect from your local machine:[/bold]")
|
|
960
|
+
console.print(f" {vscode_cmd}")
|
|
961
|
+
console.print("")
|
|
962
|
+
for line in output.split('\n'):
|
|
963
|
+
if 'vscode.dev/tunnel' in line:
|
|
964
|
+
console.print(f"[dim]Or open in browser: {line.strip()}[/dim]")
|
|
965
|
+
console.print(f"[dim]Use 'devs tunnel {dev_name} --status' to check status[/dim]")
|
|
966
|
+
console.print(f"[dim]Use 'devs tunnel {dev_name} --kill' to stop[/dim]")
|
|
967
|
+
return
|
|
968
|
+
|
|
969
|
+
if 'log in' in output.lower() or 'device' in output.lower() or 'invalid' in output.lower():
|
|
970
|
+
console.print("[yellow]Authentication required.[/yellow]")
|
|
971
|
+
console.print(f" Run: devs tunnel {dev_name} --auth")
|
|
972
|
+
subprocess.run(
|
|
973
|
+
['docker', 'exec', container_name, '/usr/local/bin/code', 'tunnel', 'kill'],
|
|
974
|
+
capture_output=True
|
|
975
|
+
)
|
|
976
|
+
return
|
|
977
|
+
|
|
978
|
+
if debug and output.strip():
|
|
979
|
+
console.print(f"[dim]Log ({i+1}s): {output.strip()[-100:]}[/dim]")
|
|
980
|
+
|
|
981
|
+
# Timeout - show what we have
|
|
982
|
+
result = subprocess.run(
|
|
983
|
+
['docker', 'exec', container_name, 'cat', log_file],
|
|
984
|
+
capture_output=True, text=True
|
|
985
|
+
)
|
|
986
|
+
if result.stdout.strip():
|
|
987
|
+
console.print("[yellow]Tunnel may still be starting. Log output:[/yellow]")
|
|
988
|
+
console.print(result.stdout.strip())
|
|
989
|
+
else:
|
|
990
|
+
console.print("[yellow]Tunnel started but no output yet. Check status with:[/yellow]")
|
|
991
|
+
console.print(f" devs tunnel {dev_name} --status")
|
|
879
992
|
|
|
880
993
|
except (DockerError, subprocess.SubprocessError) as e:
|
|
881
994
|
raise ContainerError(f"Failed to start tunnel in {dev_name}: {e}")
|
|
@@ -87,9 +87,9 @@ RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhisto
|
|
|
87
87
|
ENV DEVCONTAINER=true
|
|
88
88
|
|
|
89
89
|
# Create workspace and config directories and set permissions
|
|
90
|
-
RUN mkdir -p /workspaces /home/node/claudeconfig /home/node/codexconfig /home/node/.vscode-server/extensions && \
|
|
90
|
+
RUN mkdir -p /workspaces /home/node/claudeconfig /home/node/codexconfig /home/node/.vscode-server/extensions /home/node/.vscode/cli && \
|
|
91
91
|
echo '[]' > /home/node/.vscode-server/extensions/extensions.json && \
|
|
92
|
-
chown -R node:node /workspaces /home/node /home/node/.vscode-server && \
|
|
92
|
+
chown -R node:node /workspaces /home/node /home/node/.vscode-server /home/node/.vscode && \
|
|
93
93
|
chmod 755 /workspaces
|
|
94
94
|
|
|
95
95
|
WORKDIR /workspaces
|
|
@@ -83,7 +83,8 @@
|
|
|
83
83
|
// "source=${localEnv:WORKSPACE_FOLDER_NAME}-vscode-extensions,target=/home/node/.vscode-server/extensions,type=volume",
|
|
84
84
|
// .vscode-server includes extension state which we don't want to keep
|
|
85
85
|
// "source=${localEnv:WORKSPACE_FOLDER_NAME}-vscode-data,target=/home/node/.vscode-server,type=volume",
|
|
86
|
-
"source=${localEnv:WORKSPACE_FOLDER_NAME}-vscode-downloads,target=/home/node/.vscode-extensions-cache-volume,type=volume"
|
|
86
|
+
"source=${localEnv:WORKSPACE_FOLDER_NAME}-vscode-downloads,target=/home/node/.vscode-extensions-cache-volume,type=volume",
|
|
87
|
+
"source=devs-vscode-cli-tunnel,target=/home/node/.vscode/cli,type=volume"
|
|
87
88
|
],
|
|
88
89
|
"remoteEnv": {
|
|
89
90
|
"NODE_OPTIONS": "--max-old-space-size=4096",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/scripts/post-create-wrapper.sh
RENAMED
|
File without changes
|
{devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/scripts/python-venv-integration.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/sudo-scripts/setup-devs-env.sh
RENAMED
|
File without changes
|
{devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/sudo-scripts/setup-vscode-cache.sh
RENAMED
|
File without changes
|
{devs_common-2.0.2 → devs_common-2.0.4}/devs_common/templates/sudo-scripts/start-services.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|