cloudx-proxy 2025.3.0__py3-none-any.whl → 2026.0.2__py3-none-any.whl
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.
- cloudx_proxy/__init__.py +1 -4
- cloudx_proxy/_version.py +2 -2
- cloudx_proxy/cli.py +59 -7
- cloudx_proxy/setup.py +336 -0
- {cloudx_proxy-2025.3.0.dist-info → cloudx_proxy-2026.0.2.dist-info}/METADATA +76 -81
- cloudx_proxy-2026.0.2.dist-info/RECORD +11 -0
- cloudx_proxy-2026.0.2.dist-info/entry_points.txt +2 -0
- cloudx_proxy-2025.3.0.dist-info/RECORD +0 -10
- cloudx_proxy-2025.3.0.dist-info/entry_points.txt +0 -2
- {cloudx_proxy-2025.3.0.dist-info → cloudx_proxy-2026.0.2.dist-info}/LICENSE +0 -0
- {cloudx_proxy-2025.3.0.dist-info → cloudx_proxy-2026.0.2.dist-info}/WHEEL +0 -0
- {cloudx_proxy-2025.3.0.dist-info → cloudx_proxy-2026.0.2.dist-info}/top_level.txt +0 -0
cloudx_proxy/__init__.py
CHANGED
cloudx_proxy/_version.py
CHANGED
@@ -12,5 +12,5 @@ __version__: str
|
|
12
12
|
__version_tuple__: VERSION_TUPLE
|
13
13
|
version_tuple: VERSION_TUPLE
|
14
14
|
|
15
|
-
__version__ = version = '
|
16
|
-
__version_tuple__ = version_tuple = (
|
15
|
+
__version__ = version = '2026.0.2'
|
16
|
+
__version_tuple__ = version_tuple = (2026, 0, 2)
|
cloudx_proxy/cli.py
CHANGED
@@ -1,24 +1,32 @@
|
|
1
1
|
import os
|
2
2
|
import sys
|
3
3
|
import click
|
4
|
+
from . import __version__
|
4
5
|
from .core import CloudXClient
|
6
|
+
from .setup import CloudXSetup
|
5
7
|
|
6
|
-
@click.
|
8
|
+
@click.group()
|
9
|
+
@click.version_option(version=__version__)
|
10
|
+
def cli():
|
11
|
+
"""CloudX Client - Connect to EC2 instances via SSM for VSCode Remote SSH."""
|
12
|
+
pass
|
13
|
+
|
14
|
+
@cli.command()
|
7
15
|
@click.argument('instance_id')
|
8
16
|
@click.argument('port', type=int, default=22)
|
9
17
|
@click.option('--profile', default='vscode', help='AWS profile to use (default: vscode)')
|
10
18
|
@click.option('--region', help='AWS region (default: from profile, or eu-west-1 if not set)')
|
11
19
|
@click.option('--key-path', help='Path to SSH public key (default: ~/.ssh/vscode/vscode.pub)')
|
12
20
|
@click.option('--aws-env', help='AWS environment directory (default: ~/.aws, use name of directory in ~/.aws/aws-envs/)')
|
13
|
-
def
|
14
|
-
"""
|
21
|
+
def connect(instance_id: str, port: int, profile: str, region: str, key_path: str, aws_env: str):
|
22
|
+
"""Connect to an EC2 instance via SSM.
|
15
23
|
|
16
24
|
INSTANCE_ID is the EC2 instance ID to connect to (e.g., i-0123456789abcdef0)
|
17
25
|
|
18
26
|
Example usage:
|
19
|
-
|
20
|
-
|
21
|
-
|
27
|
+
cloudx-proxy i-0123456789abcdef0 22
|
28
|
+
cloudx-proxy i-0123456789abcdef0 22 --profile myprofile --region eu-west-1
|
29
|
+
cloudx-proxy i-0123456789abcdef0 22 --aws-env prod
|
22
30
|
"""
|
23
31
|
try:
|
24
32
|
client = CloudXClient(
|
@@ -37,5 +45,49 @@ def main(instance_id: str, port: int, profile: str, region: str, key_path: str,
|
|
37
45
|
print(f"Error: {str(e)}", file=sys.stderr)
|
38
46
|
sys.exit(1)
|
39
47
|
|
48
|
+
@cli.command()
|
49
|
+
@click.option('--profile', default='vscode', help='AWS profile to use (default: vscode)')
|
50
|
+
@click.option('--ssh-key', default='vscode', help='SSH key name to use (default: vscode)')
|
51
|
+
@click.option('--aws-env', help='AWS environment directory (default: ~/.aws, use name of directory in ~/.aws/aws-envs/)')
|
52
|
+
def setup(profile: str, ssh_key: str, aws_env: str):
|
53
|
+
"""Set up AWS profile, SSH keys, and configuration for CloudX.
|
54
|
+
|
55
|
+
This command will:
|
56
|
+
1. Set up AWS profile with credentials
|
57
|
+
2. Create or use existing SSH key
|
58
|
+
3. Configure SSH for CloudX instances
|
59
|
+
4. Check instance setup status
|
60
|
+
|
61
|
+
Example usage:
|
62
|
+
cloudx-proxy setup
|
63
|
+
cloudx-proxy setup --profile myprofile --ssh-key mykey
|
64
|
+
"""
|
65
|
+
try:
|
66
|
+
setup = CloudXSetup(profile=profile, ssh_key=ssh_key, aws_env=aws_env)
|
67
|
+
|
68
|
+
# Set up AWS profile
|
69
|
+
if not setup.setup_aws_profile():
|
70
|
+
sys.exit(1)
|
71
|
+
|
72
|
+
# Set up SSH key
|
73
|
+
if not setup.setup_ssh_key():
|
74
|
+
sys.exit(1)
|
75
|
+
|
76
|
+
# Get CloudX environment and instance details
|
77
|
+
cloudx_env = click.prompt("Enter CloudX environment (e.g., dev, prod)", type=str)
|
78
|
+
instance_id = click.prompt("Enter EC2 instance ID (e.g., i-0123456789abcdef0)", type=str)
|
79
|
+
hostname = click.prompt("Enter hostname for the instance", type=str)
|
80
|
+
|
81
|
+
# Set up SSH config
|
82
|
+
if not setup.setup_ssh_config(cloudx_env, instance_id, hostname):
|
83
|
+
sys.exit(1)
|
84
|
+
|
85
|
+
# Check instance setup status
|
86
|
+
setup.wait_for_setup_completion(instance_id)
|
87
|
+
|
88
|
+
except Exception as e:
|
89
|
+
print(f"Error: {str(e)}", file=sys.stderr)
|
90
|
+
sys.exit(1)
|
91
|
+
|
40
92
|
if __name__ == '__main__':
|
41
|
-
|
93
|
+
cli()
|
cloudx_proxy/setup.py
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
import os
|
2
|
+
import time
|
3
|
+
import json
|
4
|
+
import subprocess
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Optional, Tuple
|
7
|
+
import boto3
|
8
|
+
from botocore.exceptions import ClientError
|
9
|
+
|
10
|
+
class CloudXSetup:
|
11
|
+
def __init__(self, profile: str = "vscode", ssh_key: str = "vscode", aws_env: str = None):
|
12
|
+
"""Initialize CloudX setup.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
profile: AWS profile name (default: "vscode")
|
16
|
+
ssh_key: SSH key name (default: "vscode")
|
17
|
+
aws_env: AWS environment directory (default: None)
|
18
|
+
"""
|
19
|
+
self.profile = profile
|
20
|
+
self.ssh_key = ssh_key
|
21
|
+
self.aws_env = aws_env
|
22
|
+
self.home_dir = str(Path.home())
|
23
|
+
self.ssh_dir = Path(self.home_dir) / ".ssh" / "vscode"
|
24
|
+
self.ssh_config_file = self.ssh_dir / "config"
|
25
|
+
self.ssh_key_file = self.ssh_dir / f"{ssh_key}"
|
26
|
+
self.using_1password = False
|
27
|
+
|
28
|
+
def setup_aws_profile(self) -> bool:
|
29
|
+
"""Set up AWS profile using aws configure command.
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
bool: True if profile was set up successfully
|
33
|
+
"""
|
34
|
+
try:
|
35
|
+
# Configure AWS environment if specified
|
36
|
+
if self.aws_env:
|
37
|
+
aws_env_dir = os.path.expanduser(f"~/.aws/aws-envs/{self.aws_env}")
|
38
|
+
os.environ["AWS_CONFIG_FILE"] = os.path.join(aws_env_dir, "config")
|
39
|
+
os.environ["AWS_SHARED_CREDENTIALS_FILE"] = os.path.join(aws_env_dir, "credentials")
|
40
|
+
|
41
|
+
# Check if profile exists
|
42
|
+
session = boto3.Session(profile_name=self.profile)
|
43
|
+
try:
|
44
|
+
session.client('sts').get_caller_identity()
|
45
|
+
print(f"AWS profile '{self.profile}' already exists and is valid.")
|
46
|
+
return True
|
47
|
+
except ClientError:
|
48
|
+
pass
|
49
|
+
|
50
|
+
# Profile doesn't exist or is invalid, set it up
|
51
|
+
print(f"Setting up AWS profile '{self.profile}'...")
|
52
|
+
print("Please enter your AWS credentials:")
|
53
|
+
|
54
|
+
# Use aws configure command
|
55
|
+
subprocess.run([
|
56
|
+
'aws', 'configure',
|
57
|
+
'--profile', self.profile
|
58
|
+
], check=True)
|
59
|
+
|
60
|
+
# Verify the profile works
|
61
|
+
session = boto3.Session(profile_name=self.profile)
|
62
|
+
identity = session.client('sts').get_caller_identity()
|
63
|
+
user_arn = identity['Arn']
|
64
|
+
|
65
|
+
if not any(part.startswith('cloudX-') for part in user_arn.split('/')):
|
66
|
+
print(f"Warning: User ARN '{user_arn}' does not match expected format cloudX-{{env}}-{{user}}")
|
67
|
+
|
68
|
+
return True
|
69
|
+
|
70
|
+
except Exception as e:
|
71
|
+
print(f"Error setting up AWS profile: {e}")
|
72
|
+
return False
|
73
|
+
|
74
|
+
def setup_ssh_key(self) -> bool:
|
75
|
+
"""Set up SSH key pair.
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
bool: True if key was set up successfully
|
79
|
+
"""
|
80
|
+
try:
|
81
|
+
# Create .ssh/vscode directory if it doesn't exist
|
82
|
+
self.ssh_dir.mkdir(parents=True, exist_ok=True)
|
83
|
+
|
84
|
+
key_exists = self.ssh_key_file.exists() and (self.ssh_key_file.with_suffix('.pub')).exists()
|
85
|
+
|
86
|
+
if key_exists:
|
87
|
+
print(f"SSH key '{self.ssh_key}' already exists.")
|
88
|
+
self.using_1password = input("Would you like to use 1Password SSH agent? (y/N): ").lower() == 'y'
|
89
|
+
if not self.using_1password:
|
90
|
+
store_in_1password = input("Would you like to store the private key in 1Password? (y/N): ").lower() == 'y'
|
91
|
+
if store_in_1password:
|
92
|
+
self._store_key_in_1password()
|
93
|
+
else:
|
94
|
+
print(f"Generating new SSH key '{self.ssh_key}'...")
|
95
|
+
subprocess.run([
|
96
|
+
'ssh-keygen',
|
97
|
+
'-t', 'ed25519',
|
98
|
+
'-f', str(self.ssh_key_file),
|
99
|
+
'-N', '' # Empty passphrase
|
100
|
+
], check=True)
|
101
|
+
|
102
|
+
self.using_1password = input("Would you like to use 1Password SSH agent? (y/N): ").lower() == 'y'
|
103
|
+
if not self.using_1password:
|
104
|
+
store_in_1password = input("Would you like to store the private key in 1Password? (y/N): ").lower() == 'y'
|
105
|
+
if store_in_1password:
|
106
|
+
self._store_key_in_1password()
|
107
|
+
|
108
|
+
return True
|
109
|
+
|
110
|
+
except Exception as e:
|
111
|
+
print(f"Error setting up SSH key: {e}")
|
112
|
+
return False
|
113
|
+
|
114
|
+
def _store_key_in_1password(self) -> bool:
|
115
|
+
"""Store SSH private key in 1Password.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
bool: True if key was stored successfully
|
119
|
+
"""
|
120
|
+
try:
|
121
|
+
subprocess.run(['op', '--version'], check=True, capture_output=True)
|
122
|
+
print("Storing private key in 1Password...")
|
123
|
+
subprocess.run([
|
124
|
+
'op', 'document', 'create',
|
125
|
+
str(self.ssh_key_file),
|
126
|
+
'--title', f'CloudX SSH Key - {self.ssh_key}'
|
127
|
+
], check=True)
|
128
|
+
return True
|
129
|
+
except subprocess.CalledProcessError:
|
130
|
+
print("Error: 1Password CLI not installed or not signed in.")
|
131
|
+
return False
|
132
|
+
|
133
|
+
def setup_ssh_config(self, cloudx_env: str, instance_id: str, hostname: str) -> bool:
|
134
|
+
"""Set up SSH config for the instance.
|
135
|
+
|
136
|
+
This method manages the SSH configuration in ~/.ssh/vscode/config, with the following behavior:
|
137
|
+
1. For a new environment (if cloudx-{env}-* doesn't exist):
|
138
|
+
Creates a base config with:
|
139
|
+
- User and key configuration
|
140
|
+
- 1Password SSH agent integration if selected
|
141
|
+
- ProxyCommand using uvx cloudx-proxy with proper parameters
|
142
|
+
|
143
|
+
2. For an existing environment:
|
144
|
+
- Skips creating duplicate environment config
|
145
|
+
- Only adds the new host entry
|
146
|
+
|
147
|
+
Example config structure:
|
148
|
+
```
|
149
|
+
# Base environment config (created only once per environment)
|
150
|
+
Host cloudx-{env}-*
|
151
|
+
User ec2-user
|
152
|
+
IdentityAgent ~/.1password/agent.sock # If using 1Password
|
153
|
+
IdentityFile ~/.ssh/vscode/key.pub # .pub for 1Password, no .pub otherwise
|
154
|
+
IdentitiesOnly yes # If using 1Password
|
155
|
+
ProxyCommand uvx cloudx-proxy connect %h %p --profile profile --aws-env env
|
156
|
+
|
157
|
+
# Host entries (added for each instance)
|
158
|
+
Host cloudx-{env}-hostname
|
159
|
+
HostName i-1234567890
|
160
|
+
```
|
161
|
+
|
162
|
+
Args:
|
163
|
+
cloudx_env: CloudX environment (e.g., dev, prod)
|
164
|
+
instance_id: EC2 instance ID
|
165
|
+
hostname: Hostname for the instance
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
bool: True if config was set up successfully
|
169
|
+
"""
|
170
|
+
try:
|
171
|
+
# Check if we need to create base config
|
172
|
+
need_base_config = True
|
173
|
+
if self.ssh_config_file.exists():
|
174
|
+
current_config = self.ssh_config_file.read_text()
|
175
|
+
# Check if configuration for this environment already exists
|
176
|
+
if f"Host cloudx-{cloudx_env}-*" in current_config:
|
177
|
+
need_base_config = False
|
178
|
+
|
179
|
+
if need_base_config:
|
180
|
+
# Build ProxyCommand with all necessary parameters
|
181
|
+
proxy_command = f"uvx cloudx-proxy connect %h %p --profile {self.profile}"
|
182
|
+
if self.aws_env:
|
183
|
+
proxy_command += f" --aws-env {self.aws_env}"
|
184
|
+
if self.ssh_key != "vscode":
|
185
|
+
proxy_command += f" --key-path {self.ssh_key_file}.pub"
|
186
|
+
|
187
|
+
# Build base configuration
|
188
|
+
base_config = f"""# CloudX SSH Configuration
|
189
|
+
Host cloudx-{cloudx_env}-*
|
190
|
+
User ec2-user
|
191
|
+
"""
|
192
|
+
# Add 1Password or standard key configuration
|
193
|
+
if self.using_1password:
|
194
|
+
base_config += f""" IdentityAgent ~/.1password/agent.sock
|
195
|
+
IdentityFile {self.ssh_key_file}.pub
|
196
|
+
IdentitiesOnly yes
|
197
|
+
"""
|
198
|
+
else:
|
199
|
+
base_config += f""" IdentityFile {self.ssh_key_file}
|
200
|
+
"""
|
201
|
+
# Add ProxyCommand
|
202
|
+
base_config += f""" ProxyCommand {proxy_command}
|
203
|
+
"""
|
204
|
+
|
205
|
+
# If file exists, append the new config, otherwise create it
|
206
|
+
if self.ssh_config_file.exists():
|
207
|
+
with open(self.ssh_config_file, 'a') as f:
|
208
|
+
f.write("\n" + base_config)
|
209
|
+
else:
|
210
|
+
self.ssh_config_file.write_text(base_config)
|
211
|
+
|
212
|
+
# Add specific host entry
|
213
|
+
host_entry = f"""
|
214
|
+
Host cloudx-{cloudx_env}-{hostname}
|
215
|
+
HostName {instance_id}
|
216
|
+
"""
|
217
|
+
with open(self.ssh_config_file, 'a') as f:
|
218
|
+
f.write(host_entry)
|
219
|
+
|
220
|
+
# Ensure main SSH config includes our config
|
221
|
+
main_config = Path(self.home_dir) / ".ssh" / "config"
|
222
|
+
include_line = f"Include {self.ssh_config_file}\n"
|
223
|
+
|
224
|
+
if main_config.exists():
|
225
|
+
content = main_config.read_text()
|
226
|
+
if include_line not in content:
|
227
|
+
with open(main_config, 'a') as f:
|
228
|
+
f.write(f"\n{include_line}")
|
229
|
+
else:
|
230
|
+
main_config.write_text(include_line)
|
231
|
+
|
232
|
+
print(f"\nSSH configuration has been set up:")
|
233
|
+
print(f"- Main config file: {main_config}")
|
234
|
+
print(f"- CloudX config file: {self.ssh_config_file}")
|
235
|
+
print(f"\nYou can now connect using: ssh cloudx-{cloudx_env}-{hostname}")
|
236
|
+
|
237
|
+
return True
|
238
|
+
|
239
|
+
except Exception as e:
|
240
|
+
print(f"Error setting up SSH config: {e}")
|
241
|
+
return False
|
242
|
+
|
243
|
+
def check_instance_setup(self, instance_id: str) -> Tuple[bool, bool]:
|
244
|
+
"""Check if instance setup is complete.
|
245
|
+
|
246
|
+
Args:
|
247
|
+
instance_id: EC2 instance ID
|
248
|
+
|
249
|
+
Returns:
|
250
|
+
Tuple[bool, bool]: (is_running, is_setup_complete)
|
251
|
+
"""
|
252
|
+
try:
|
253
|
+
session = boto3.Session(profile_name=self.profile)
|
254
|
+
ssm = session.client('ssm')
|
255
|
+
|
256
|
+
# Check if instance is online in SSM
|
257
|
+
response = ssm.describe_instance_information(
|
258
|
+
Filters=[{'Key': 'InstanceIds', 'Values': [instance_id]}]
|
259
|
+
)
|
260
|
+
is_running = bool(response['InstanceInformationList'])
|
261
|
+
|
262
|
+
if not is_running:
|
263
|
+
return False, False
|
264
|
+
|
265
|
+
# Check setup status using SSM command
|
266
|
+
response = ssm.send_command(
|
267
|
+
InstanceIds=[instance_id],
|
268
|
+
DocumentName='AWS-RunShellScript',
|
269
|
+
Parameters={
|
270
|
+
'commands': [
|
271
|
+
'test -f /home/ec2-user/.install-done && echo "DONE" || '
|
272
|
+
'test -f /home/ec2-user/.install-running && echo "RUNNING" || '
|
273
|
+
'echo "NOT_STARTED"'
|
274
|
+
]
|
275
|
+
}
|
276
|
+
)
|
277
|
+
|
278
|
+
command_id = response['Command']['CommandId']
|
279
|
+
|
280
|
+
# Wait for command completion
|
281
|
+
for _ in range(10): # 10 second timeout
|
282
|
+
time.sleep(1)
|
283
|
+
result = ssm.get_command_invocation(
|
284
|
+
CommandId=command_id,
|
285
|
+
InstanceId=instance_id
|
286
|
+
)
|
287
|
+
if result['Status'] in ['Success', 'Failed']:
|
288
|
+
break
|
289
|
+
|
290
|
+
is_setup_complete = result['Status'] == 'Success' and result['StandardOutputContent'].strip() == 'DONE'
|
291
|
+
|
292
|
+
return True, is_setup_complete
|
293
|
+
|
294
|
+
except Exception as e:
|
295
|
+
print(f"Error checking instance setup: {e}")
|
296
|
+
return False, False
|
297
|
+
|
298
|
+
def wait_for_setup_completion(self, instance_id: str) -> bool:
|
299
|
+
"""Wait for instance setup to complete.
|
300
|
+
|
301
|
+
Args:
|
302
|
+
instance_id: EC2 instance ID
|
303
|
+
|
304
|
+
Returns:
|
305
|
+
bool: True if setup completed successfully
|
306
|
+
"""
|
307
|
+
print(f"Checking setup status for instance {instance_id}...")
|
308
|
+
|
309
|
+
is_running, is_complete = self.check_instance_setup(instance_id)
|
310
|
+
|
311
|
+
if not is_running:
|
312
|
+
print("Error: Instance is not running or not accessible via SSM")
|
313
|
+
return False
|
314
|
+
|
315
|
+
if is_complete:
|
316
|
+
print("Instance setup is already complete")
|
317
|
+
return True
|
318
|
+
|
319
|
+
wait = input("Instance setup is not complete. Would you like to wait? (Y/n): ").lower() != 'n'
|
320
|
+
if not wait:
|
321
|
+
return False
|
322
|
+
|
323
|
+
print("Waiting for setup to complete", end='', flush=True)
|
324
|
+
while True:
|
325
|
+
is_running, is_complete = self.check_instance_setup(instance_id)
|
326
|
+
|
327
|
+
if not is_running:
|
328
|
+
print("\nError: Instance is no longer running or accessible")
|
329
|
+
return False
|
330
|
+
|
331
|
+
if is_complete:
|
332
|
+
print("\nInstance setup completed successfully")
|
333
|
+
return True
|
334
|
+
|
335
|
+
print(".", end='', flush=True)
|
336
|
+
time.sleep(10)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: cloudx-proxy
|
3
|
-
Version:
|
3
|
+
Version: 2026.0.2
|
4
4
|
Summary: SSH proxy command to connect VSCode with Cloud9/CloudX instance using AWS Systems Manager
|
5
5
|
Author-email: easytocloud <info@easytocloud.com>
|
6
6
|
License: MIT License
|
@@ -26,7 +26,7 @@ License: MIT License
|
|
26
26
|
SOFTWARE.
|
27
27
|
|
28
28
|
Project-URL: Homepage, https://github.com/easytocloud/cloudX-proxy
|
29
|
-
Project-URL: Repository, https://github.com/easytocloud/cloudX-proxy
|
29
|
+
Project-URL: Repository, https://github.com/easytocloud/cloudX-proxy
|
30
30
|
Project-URL: Issues, https://github.com/easytocloud/cloudX-proxy/issues
|
31
31
|
Project-URL: Changelog, https://github.com/easytocloud/cloudX-proxy/blob/main/CHANGELOG.md
|
32
32
|
Keywords: aws,vscode,cloud9,cloudX,ssm,ssh,proxy
|
@@ -93,81 +93,64 @@ The proxy also supports easytocloud's AWS profile organizer. If you use multiple
|
|
93
93
|
|
94
94
|
## Setup
|
95
95
|
|
96
|
-
|
97
|
-
2. Configure the 'vscode' AWS profile with the credentials from the cloudX-user stack
|
98
|
-
3. Install uv if you haven't already:
|
99
|
-
```bash
|
100
|
-
pip install uv
|
101
|
-
```
|
96
|
+
cloudX-proxy now includes a setup command that automates the entire configuration process:
|
102
97
|
|
103
|
-
|
104
|
-
|
105
|
-
-
|
106
|
-
- Installs all dependencies
|
107
|
-
- Runs the code
|
98
|
+
```bash
|
99
|
+
# Basic setup with defaults (vscode profile and key)
|
100
|
+
uvx cloudx-proxy setup
|
108
101
|
|
109
|
-
|
102
|
+
# Setup with custom profile and key
|
103
|
+
uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
|
110
104
|
|
111
|
-
|
105
|
+
# Setup with AWS environment
|
106
|
+
uvx cloudx-proxy setup --aws-env prod
|
107
|
+
```
|
112
108
|
|
113
|
-
|
109
|
+
The setup command will:
|
114
110
|
|
115
|
-
|
111
|
+
1. Configure AWS Profile:
|
112
|
+
- Creates/validates AWS profile with cloudX-{env}-{user} format
|
113
|
+
- Supports AWS environment directories via --aws-env
|
114
|
+
- Uses aws configure for credential input
|
116
115
|
|
117
|
-
|
118
|
-
|
119
|
-
|
116
|
+
2. Manage SSH Keys:
|
117
|
+
- Creates new SSH key pair if needed
|
118
|
+
- Offers 1Password integration options:
|
119
|
+
* Using 1Password SSH agent
|
120
|
+
* Storing private key as 1Password document
|
120
121
|
|
121
|
-
|
122
|
-
|
123
|
-
|
122
|
+
3. Configure SSH:
|
123
|
+
- Creates ~/.ssh/vscode/config with proper settings
|
124
|
+
- Sets up environment-specific configurations
|
125
|
+
- Configures ProxyCommand with all necessary parameters
|
126
|
+
- Ensures main ~/.ssh/config includes the configuration
|
124
127
|
|
125
|
-
|
128
|
+
4. Verify Instance Setup:
|
129
|
+
- Checks instance setup status
|
130
|
+
- Offers to wait for setup completion
|
131
|
+
- Monitors setup progress
|
126
132
|
|
127
|
-
|
133
|
+
### Example SSH Configuration
|
128
134
|
|
129
|
-
|
130
|
-
```bash
|
131
|
-
# Include VSCode-specific config
|
132
|
-
Include vscode/config
|
133
|
-
```
|
135
|
+
The setup command generates a configuration structure like this:
|
134
136
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
#### Windows
|
150
|
-
|
151
|
-
1. Create or edit `%USERPROFILE%\.ssh\config`:
|
152
|
-
```
|
153
|
-
Include vscode/config
|
154
|
-
```
|
137
|
+
```
|
138
|
+
# Base environment config (created once per environment)
|
139
|
+
Host cloudx-{env}-*
|
140
|
+
User ec2-user
|
141
|
+
IdentityAgent ~/.1password/agent.sock # If using 1Password
|
142
|
+
IdentityFile ~/.ssh/vscode/key.pub # .pub for 1Password, no .pub otherwise
|
143
|
+
IdentitiesOnly yes # If using 1Password
|
144
|
+
ProxyCommand uvx cloudx-proxy connect %h %p --profile profile --aws-env env
|
145
|
+
|
146
|
+
# Host entries (added for each instance)
|
147
|
+
Host cloudx-{env}-hostname
|
148
|
+
HostName i-1234567890
|
149
|
+
```
|
155
150
|
|
156
|
-
|
157
|
-
```
|
158
|
-
Host cloudx-*
|
159
|
-
ProxyCommand uvx cloudx-proxy %h %p
|
160
|
-
User ec2-user
|
161
|
-
IdentityFile %USERPROFILE%\.ssh\vscode\vscode
|
162
|
-
StrictHostKeyChecking no
|
163
|
-
UserKnownHostsFile /dev/null
|
164
|
-
|
165
|
-
# Example host configuration
|
166
|
-
Host cloudx-dev
|
167
|
-
HostName i-0123456789abcdef0 # Your EC2 instance ID
|
168
|
-
```
|
151
|
+
When adding new instances to an existing environment, the setup command will only add the specific host entry, preserving the existing environment configuration.
|
169
152
|
|
170
|
-
###
|
153
|
+
### VSCode Configuration
|
171
154
|
|
172
155
|
1. Install the "Remote - SSH" extension in VSCode
|
173
156
|
2. Configure VSCode settings:
|
@@ -183,23 +166,23 @@ ssh-keygen -t rsa -b 4096 -f ~/.ssh/vscode/vscode
|
|
183
166
|
### Command Line
|
184
167
|
|
185
168
|
```bash
|
186
|
-
#
|
187
|
-
uvx cloudx-proxy
|
169
|
+
# Setup new environment and instance
|
170
|
+
uvx cloudx-proxy setup --profile myprofile --aws-env prod
|
188
171
|
|
189
|
-
#
|
190
|
-
uvx cloudx-proxy
|
172
|
+
# Add instance to existing environment
|
173
|
+
uvx cloudx-proxy setup --profile myprofile --aws-env prod
|
191
174
|
|
192
|
-
#
|
193
|
-
uvx cloudx-proxy i-0123456789abcdef0 --profile myprofile
|
175
|
+
# Connect to instance
|
176
|
+
uvx cloudx-proxy connect i-0123456789abcdef0 22 --profile myprofile --aws-env prod
|
194
177
|
|
195
|
-
#
|
196
|
-
uvx cloudx-proxy i-0123456789abcdef0 --
|
178
|
+
# Connect with custom port
|
179
|
+
uvx cloudx-proxy connect i-0123456789abcdef0 2222 --profile myprofile
|
197
180
|
|
198
|
-
#
|
199
|
-
uvx cloudx-proxy i-0123456789abcdef0 --
|
181
|
+
# Connect with different region
|
182
|
+
uvx cloudx-proxy connect i-0123456789abcdef0 22 --region us-east-1
|
200
183
|
|
201
|
-
#
|
202
|
-
uvx cloudx-proxy i-0123456789abcdef0 --key-path ~/.ssh/custom_key.pub
|
184
|
+
# Connect with custom key
|
185
|
+
uvx cloudx-proxy connect i-0123456789abcdef0 22 --key-path ~/.ssh/custom_key.pub
|
203
186
|
```
|
204
187
|
|
205
188
|
### VSCode
|
@@ -235,24 +218,36 @@ The AWS user/role needs these permissions:
|
|
235
218
|
|
236
219
|
## Troubleshooting
|
237
220
|
|
238
|
-
1. **
|
221
|
+
1. **Setup Issues**
|
222
|
+
- If AWS profile validation fails, ensure your user ARN matches the cloudX-{env}-{user} format
|
223
|
+
- For 1Password integration, ensure the CLI is installed and you're signed in
|
224
|
+
- Check that ~/.ssh/vscode directory has proper permissions (700)
|
225
|
+
- Verify main ~/.ssh/config is writable
|
226
|
+
|
227
|
+
2. **Connection Timeout**
|
239
228
|
- Ensure the instance has the SSM agent installed and running
|
240
229
|
- Check that your AWS credentials have the required permissions
|
241
230
|
- Verify the instance ID is correct
|
242
231
|
- Increase the VSCode SSH timeout if needed
|
243
232
|
|
244
|
-
|
245
|
-
-
|
233
|
+
3. **SSH Key Issues**
|
234
|
+
- If using 1Password SSH agent, verify agent is running (~/.1password/agent.sock exists)
|
246
235
|
- Check file permissions (600 for private key, 644 for public key)
|
247
236
|
- Verify the public key is being successfully pushed to the instance
|
237
|
+
- For stored keys in 1Password, ensure you can access them via the CLI
|
248
238
|
|
249
|
-
|
250
|
-
- Confirm AWS CLI is configured with valid credentials
|
239
|
+
4. **AWS Configuration**
|
240
|
+
- Confirm AWS CLI is configured with valid credentials
|
251
241
|
- Default region is eu-west-1 if not specified in profile or command line
|
252
242
|
- If using AWS profile organizer, ensure your environment directory exists at `~/.aws/aws-envs/<environment>/`
|
253
243
|
- Verify the Session Manager plugin is installed correctly
|
254
244
|
- Check that the instance has the required IAM role for SSM
|
255
245
|
|
246
|
+
5. **Instance Setup Status**
|
247
|
+
- If setup appears stuck, check /home/ec2-user/.install-running exists
|
248
|
+
- Verify /home/ec2-user/.install-done is created upon completion
|
249
|
+
- Check instance system logs for setup script errors
|
250
|
+
|
256
251
|
## License
|
257
252
|
|
258
253
|
MIT License - see LICENSE file for details
|
@@ -0,0 +1,11 @@
|
|
1
|
+
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
+
cloudx_proxy/_version.py,sha256=5qpRRzcvqdWRxS4N-2BefN-qS2k4DLuS65JCN34TY0A,417
|
3
|
+
cloudx_proxy/cli.py,sha256=zqOIynqNr76JE6VRF7ifn3Fr0Z5C6CjnAHQgupF30BU,3374
|
4
|
+
cloudx_proxy/core.py,sha256=j6CUKdg2Ikcoi-05ceXMGA_c1aGWBhN9_JevbkLkaUY,7383
|
5
|
+
cloudx_proxy/setup.py,sha256=5GGfPgIiEWwkFEY-HnnPvT_8iD94WqjoFjJ5tGSHz8o,13157
|
6
|
+
cloudx_proxy-2026.0.2.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
+
cloudx_proxy-2026.0.2.dist-info/METADATA,sha256=C7ISmMQzb80SXyke-z0wZifp650UEmKC-xav1ibcI14,10219
|
8
|
+
cloudx_proxy-2026.0.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
9
|
+
cloudx_proxy-2026.0.2.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
+
cloudx_proxy-2026.0.2.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
+
cloudx_proxy-2026.0.2.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
cloudx_proxy/__init__.py,sha256=YfZd1x9PT_aGA4FGsNw1WwvWkfRKLy_9O93a_W2wtiE,187
|
2
|
-
cloudx_proxy/_version.py,sha256=fWRR5Orqc8dqtVKNeAfCqsDhc30HwoGblv4fyeGnKoY,417
|
3
|
-
cloudx_proxy/cli.py,sha256=bBcvqiKpsLY6cYt-OG8282RZuoSDwDgEdyehTAeeec0,1502
|
4
|
-
cloudx_proxy/core.py,sha256=j6CUKdg2Ikcoi-05ceXMGA_c1aGWBhN9_JevbkLkaUY,7383
|
5
|
-
cloudx_proxy-2025.3.0.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
6
|
-
cloudx_proxy-2025.3.0.dist-info/METADATA,sha256=I-ImXVJrHCT-tnl26xPK94FrtVwxZ-dgc7290pzp0rs,9527
|
7
|
-
cloudx_proxy-2025.3.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
8
|
-
cloudx_proxy-2025.3.0.dist-info/entry_points.txt,sha256=BugqsX1eJtZGC6b5ONbieB9To9go2RCQVXuzdN79lZQ,55
|
9
|
-
cloudx_proxy-2025.3.0.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
10
|
-
cloudx_proxy-2025.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|