cloudx-proxy 0.4.2__py3-none-any.whl → 0.4.4__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/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.4.2'
21
- __version_tuple__ = version_tuple = (0, 4, 2)
20
+ __version__ = version = '0.4.4'
21
+ __version_tuple__ = version_tuple = (0, 4, 4)
cloudx_proxy/cli.py CHANGED
@@ -40,6 +40,8 @@ def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str
40
40
  aws_env=aws_env
41
41
  )
42
42
 
43
+ client.log(f"cloudx-proxy@{__version__} Connecting to instance {instance_id} on port {port}...")
44
+
43
45
  if not client.connect():
44
46
  sys.exit(1)
45
47
 
cloudx_proxy/setup.py CHANGED
@@ -346,6 +346,18 @@ class CloudXSetup:
346
346
  return True
347
347
  return False
348
348
 
349
+ def _get_version(self) -> str:
350
+ """Get the current version of the cloudx-proxy package.
351
+
352
+ Returns:
353
+ str: Version string
354
+ """
355
+ try:
356
+ from . import __version__
357
+ return __version__
358
+ except (ImportError, AttributeError):
359
+ return "unknown"
360
+
349
361
  def _build_proxy_command(self) -> str:
350
362
  """Build the ProxyCommand with appropriate parameters.
351
363
 
@@ -383,38 +395,138 @@ class CloudXSetup:
383
395
  IdentitiesOnly yes
384
396
  """
385
397
 
386
- def _build_host_config(self, cloudx_env: str, hostname: str, instance_id: str, include_proxy: bool = True) -> str:
387
- """Build a host configuration block.
398
+ def _get_timestamp(self) -> str:
399
+ """Get a formatted timestamp for configuration comments.
400
+
401
+ Returns:
402
+ str: Formatted timestamp
403
+ """
404
+ from datetime import datetime
405
+ return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
406
+
407
+ def _build_generic_config(self) -> str:
408
+ """Build a generic configuration block with common settings for all environments.
409
+
410
+ Returns:
411
+ str: Generic configuration block
412
+ """
413
+ version = self._get_version()
414
+ timestamp = self._get_timestamp()
415
+
416
+ # Start with metadata comment
417
+ config = f"""
418
+ # Created by cloudx-proxy v{version} on {timestamp}
419
+ # Configuration type: generic
420
+ Host cloudx-*
421
+ User ec2-user
422
+ TCPKeepAlive yes
423
+ """
424
+
425
+ # Add SSH multiplexing configuration
426
+ control_path = "~/.ssh/control/%r@%h:%p"
427
+ if platform.system() == 'Windows':
428
+ # Use forward slashes for Windows as well, SSH client will handle conversion
429
+ control_path = "~/.ssh/control/%r@%h:%p"
430
+
431
+ config += f""" ControlMaster auto
432
+ ControlPath {control_path}
433
+ ControlPersist 4h
434
+ """
435
+
436
+ return config
437
+
438
+ def _build_environment_config(self, cloudx_env: str) -> str:
439
+ """Build an environment-specific configuration block.
388
440
 
389
441
  Args:
390
442
  cloudx_env: CloudX environment
391
- hostname: Hostname for the instance
392
- instance_id: EC2 instance ID (None for wildcard entries)
393
- include_proxy: Whether to include the ProxyCommand (default: True)
394
443
 
395
444
  Returns:
396
- str: Complete host configuration block
445
+ str: Environment configuration block
397
446
  """
398
- host_pattern = hostname if hostname else "*"
399
- host_entry = f"""
400
- Host cloudx-{cloudx_env}-{host_pattern}
401
- """
402
- # Add HostName only for specific hosts, not for wildcard entries
403
- if instance_id:
404
- host_entry += f""" HostName {instance_id}
405
- """
406
- host_entry += """ User ec2-user
447
+ version = self._get_version()
448
+ timestamp = self._get_timestamp()
449
+
450
+ # Start with metadata comment
451
+ config = f"""
452
+ # Created by cloudx-proxy v{version} on {timestamp}
453
+ # Configuration type: environment
454
+ Host cloudx-{cloudx_env}-*
407
455
  """
408
456
  # Add authentication configuration
409
- host_entry += self._build_auth_config()
457
+ config += self._build_auth_config()
410
458
 
411
- # Add proxy command if requested
412
- if include_proxy:
413
- host_entry += f""" ProxyCommand {self._build_proxy_command()}
459
+ # Add ProxyCommand
460
+ config += f""" ProxyCommand {self._build_proxy_command()}
414
461
  """
415
462
 
416
- return host_entry
463
+ return config
417
464
 
465
+ def _build_host_config(self, cloudx_env: str, hostname: str, instance_id: str) -> str:
466
+ """Build a host-specific configuration block.
467
+
468
+ Args:
469
+ cloudx_env: CloudX environment
470
+ hostname: Hostname for the instance
471
+ instance_id: EC2 instance ID
472
+
473
+ Returns:
474
+ str: Host configuration block
475
+ """
476
+ version = self._get_version()
477
+ timestamp = self._get_timestamp()
478
+
479
+ # Start with metadata comment
480
+ config = f"""
481
+ # Created by cloudx-proxy v{version} on {timestamp}
482
+ # Configuration type: host
483
+ Host cloudx-{cloudx_env}-{hostname}
484
+ HostName {instance_id}
485
+ """
486
+
487
+ return config
488
+
489
+ def _check_config_exists(self, pattern: str, current_config: str) -> bool:
490
+ """Check if a configuration pattern exists in the current config.
491
+
492
+ Args:
493
+ pattern: Host pattern to look for (e.g., 'cloudx-*', 'cloudx-dev-*')
494
+ current_config: Current SSH config content
495
+
496
+ Returns:
497
+ bool: True if pattern exists in configuration
498
+ """
499
+ return f"Host {pattern}" in current_config
500
+
501
+ def _extract_host_config(self, pattern: str, current_config: str) -> Tuple[str, str]:
502
+ """Extract a host configuration block from the current config.
503
+
504
+ Args:
505
+ pattern: Host pattern to extract (e.g., 'cloudx-*', 'cloudx-dev-*')
506
+ current_config: Current SSH config content
507
+
508
+ Returns:
509
+ Tuple[str, str]: Extracted host configuration, remaining configuration
510
+ """
511
+ lines = current_config.splitlines()
512
+ host_config_lines = []
513
+ remaining_lines = []
514
+ in_host_block = False
515
+
516
+ for line in lines:
517
+ if line.strip() == f"Host {pattern}":
518
+ in_host_block = True
519
+ host_config_lines.append(line)
520
+ elif in_host_block and line.strip().startswith("Host "):
521
+ in_host_block = False
522
+ remaining_lines.append(line)
523
+ elif in_host_block:
524
+ host_config_lines.append(line)
525
+ else:
526
+ remaining_lines.append(line)
527
+
528
+ return "\n".join(host_config_lines), "\n".join(remaining_lines)
529
+
418
530
  def _add_host_entry(self, cloudx_env: str, instance_id: str, hostname: str, current_config: str) -> bool:
419
531
  """Add settings to a specific host entry.
420
532
 
@@ -428,13 +540,29 @@ Host cloudx-{cloudx_env}-{host_pattern}
428
540
  bool: True if settings were added successfully
429
541
  """
430
542
  try:
431
- # Generate the host entry using the consolidated helper method
432
- host_entry = self._build_host_config(cloudx_env, hostname, instance_id)
433
-
434
- # Append host entry
435
- with open(self.ssh_config_file, 'a') as f:
436
- f.write(host_entry)
437
- self.print_status("Host entry added with settings", True, 2)
543
+ # Check if host entry already exists
544
+ host_pattern = f"cloudx-{cloudx_env}-{hostname}"
545
+ if self._check_config_exists(host_pattern, current_config):
546
+ # Extract existing host configuration
547
+ host_config, remaining_config = self._extract_host_config(host_pattern, current_config)
548
+
549
+ # Update host configuration
550
+ host_config = self._build_host_config(cloudx_env, hostname, instance_id)
551
+
552
+ # Write updated config
553
+ with open(self.ssh_config_file, 'w') as f:
554
+ f.write(remaining_config)
555
+ f.write(host_config)
556
+
557
+ self.print_status(f"Updated existing host entry for {host_pattern}", True, 2)
558
+ else:
559
+ # Generate new host entry
560
+ host_entry = self._build_host_config(cloudx_env, hostname, instance_id)
561
+
562
+ # Append host entry
563
+ with open(self.ssh_config_file, 'a') as f:
564
+ f.write(host_entry)
565
+ self.print_status(f"Added new host entry for {host_pattern}", True, 2)
438
566
 
439
567
  # Set proper permissions on the config file
440
568
  if platform.system() != 'Windows':
@@ -451,6 +579,81 @@ Host cloudx-{cloudx_env}-{host_pattern}
451
579
  self.print_status("Continuing setup despite SSH config issues", None, 2)
452
580
  return True
453
581
  return False
582
+
583
+ def _check_and_create_generic_config(self, current_config: str) -> Tuple[bool, str]:
584
+ """Check if generic configuration exists and create it if needed.
585
+
586
+ Args:
587
+ current_config: Current SSH config content
588
+
589
+ Returns:
590
+ Tuple[bool, str]: Success flag, Updated configuration
591
+ """
592
+ if self._check_config_exists("cloudx-*", current_config):
593
+ self.print_status("Found existing generic config for cloudx-*", True, 2)
594
+ return True, current_config
595
+
596
+ self.print_status("Creating generic config for cloudx-*", None, 2)
597
+ generic_config = self._build_generic_config()
598
+
599
+ # Append generic config to current config
600
+ updated_config = current_config
601
+ if updated_config and not updated_config.endswith('\n'):
602
+ updated_config += '\n'
603
+ updated_config += generic_config
604
+
605
+ return True, updated_config
606
+
607
+ def _check_and_create_environment_config(self, cloudx_env: str, current_config: str) -> Tuple[bool, str]:
608
+ """Check if environment configuration exists and create it if needed.
609
+
610
+ Args:
611
+ cloudx_env: CloudX environment
612
+ current_config: Current SSH config content
613
+
614
+ Returns:
615
+ Tuple[bool, str]: Success flag, Updated configuration
616
+ """
617
+ if self._check_config_exists(f"cloudx-{cloudx_env}-*", current_config):
618
+ self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
619
+
620
+ # Option to override if needed
621
+ choice = self.prompt(
622
+ "Would you like to \n"
623
+ " 1: override the existing environment config\n"
624
+ " 2: keep existing environment config?\n"
625
+ "Select an option",
626
+ "2"
627
+ )
628
+
629
+ if choice == "1":
630
+ # Remove existing config for this environment
631
+ self.print_status("Removing existing environment configuration", None, 2)
632
+ env_config, remaining_config = self._extract_host_config(f"cloudx-{cloudx_env}-*", current_config)
633
+
634
+ # Create new environment config
635
+ env_config = self._build_environment_config(cloudx_env)
636
+
637
+ # Append new environment config to remaining config
638
+ updated_config = remaining_config
639
+ if updated_config and not updated_config.endswith('\n'):
640
+ updated_config += '\n'
641
+ updated_config += env_config
642
+
643
+ return True, updated_config
644
+
645
+ return True, current_config
646
+
647
+ self.print_status(f"Creating environment config for cloudx-{cloudx_env}-*", None, 2)
648
+ env_config = self._build_environment_config(cloudx_env)
649
+
650
+ # Append environment config to current config
651
+ updated_config = current_config
652
+ if updated_config and not updated_config.endswith('\n'):
653
+ updated_config += '\n'
654
+ updated_config += env_config
655
+
656
+ return True, updated_config
454
657
 
455
658
  def _ensure_control_dir(self) -> bool:
456
659
  """Create SSH control directory with proper permissions.
@@ -478,38 +681,21 @@ Host cloudx-{cloudx_env}-{host_pattern}
478
681
  return False
479
682
 
480
683
  def setup_ssh_config(self, cloudx_env: str, instance_id: str, hostname: str) -> bool:
481
- """Set up SSH config for the instance.
482
-
483
- This method manages the SSH configuration in ~/.ssh/vscode/config, with the following behavior:
484
- 1. For a new environment (if cloudx-{env}-* doesn't exist):
485
- Creates a base config with:
486
- - User and key configuration
487
- - 1Password SSH agent integration if selected
488
- - ProxyCommand using uvx cloudx-proxy with proper parameters
489
- - SSH multiplexing configuration (ControlMaster, ControlPath, ControlPersist)
490
-
491
- 2. For an existing environment:
492
- - Skips creating duplicate environment config
493
- - Only adds the new host entry
494
-
495
- Example config structure:
496
- ```
497
- # Base environment config (created only once per environment)
498
- Host cloudx-{env}-*
499
- User ec2-user
500
- IdentityAgent ~/.1password/agent.sock # If using 1Password
501
- IdentityFile ~/.ssh/vscode/key.pub # .pub for 1Password, no .pub otherwise
502
- IdentitiesOnly yes # If using 1Password
503
- TCPKeepAlive yes
504
- ControlMaster auto
505
- ControlPath ~/.ssh/control/%r@%h:%p
506
- ControlPersist 4h
507
- ProxyCommand uvx cloudx-proxy connect %h %p --profile profile --aws-env env
508
-
509
- # Host entries (added for each instance)
510
- Host cloudx-{env}-hostname
511
- HostName i-1234567890
512
- ```
684
+ """Set up SSH config for the instance using a three-tier configuration approach.
685
+
686
+ This method implements a hierarchical SSH configuration with three levels:
687
+ 1. Generic (cloudx-*): Common settings for all environments
688
+ - User settings
689
+ - TCP keepalive
690
+ - SSH multiplexing configuration
691
+
692
+ 2. Environment (cloudx-{env}-*): Environment-specific settings
693
+ - Authentication configuration (identity settings)
694
+ - ProxyCommand with environment-specific parameters
695
+
696
+ 3. Host (cloudx-{env}-hostname): Instance-specific settings
697
+ - HostName (instance ID)
698
+ - Optional overrides for incompatible settings
513
699
 
514
700
  Args:
515
701
  cloudx_env: CloudX environment (e.g., dev, prod)
@@ -520,91 +706,46 @@ Host cloudx-{cloudx_env}-{host_pattern}
520
706
  bool: True if config was set up successfully
521
707
  """
522
708
  self.print_header("SSH Configuration")
523
- self.print_status("Setting up SSH configuration...")
709
+ self.print_status("Setting up SSH configuration with three-tier approach...")
524
710
 
525
711
  try:
526
- # Check existing configuration
527
- if self.ssh_config_file.exists():
528
- current_config = self.ssh_config_file.read_text()
529
- # Check if configuration for this environment already exists
530
- if f"Host cloudx-{cloudx_env}-*" in current_config:
531
- self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
532
- choice = self.prompt(
533
- "Would you like to \n"
534
- " 1: override the existing config\n"
535
- " 2: add settings to the specific host entry?\n"
536
- "Select an option",
537
- "1"
538
- )
539
- if choice == "2":
540
- # Add settings to specific host entry
541
- self.print_status("Adding settings to specific host entry", None, 2)
542
- return self._add_host_entry(cloudx_env, instance_id, hostname, current_config)
543
- else:
544
- # Remove existing config for this environment
545
- self.print_status("Removing existing configuration", None, 2)
546
- lines = current_config.splitlines()
547
- new_lines = []
548
- skip = False
549
- for line in lines:
550
- if line.strip() == f"Host cloudx-{cloudx_env}-*":
551
- skip = True
552
- elif skip and line.startswith("Host "):
553
- skip = False
554
- if not skip:
555
- new_lines.append(line)
556
- current_config = "\n".join(new_lines)
557
- with open(self.ssh_config_file, 'w') as f:
558
- f.write(current_config)
559
-
560
- # Create base config
561
- self.print_status(f"Creating new config for cloudx-{cloudx_env}-*", None, 2)
562
-
563
712
  # Ensure control directory exists with proper permissions
564
713
  if not self._ensure_control_dir():
565
714
  return False
566
715
 
567
- # Build base configuration with wildcard hostname pattern
568
- # Start with a header comment
569
- base_config = "# cloudx-proxy SSH Configuration\n"
716
+ # Initialize or read current configuration
717
+ current_config = ""
718
+ if self.ssh_config_file.exists():
719
+ current_config = self.ssh_config_file.read_text()
570
720
 
571
- # Add base host pattern with wildcard
572
- base_config += self._build_host_config(cloudx_env, None, None, include_proxy=True)
721
+ # 1. Check and create generic config (highest level)
722
+ self.print_status("Checking generic configuration...", None, 2)
723
+ success, current_config = self._check_and_create_generic_config(current_config)
724
+ if not success:
725
+ return False
573
726
 
574
- # Add SSH multiplexing configuration
575
- control_path = "~/.ssh/control/%r@%h:%p"
576
- if platform.system() == 'Windows':
577
- # Use forward slashes for Windows as well, SSH client will handle conversion
578
- control_path = "~/.ssh/control/%r@%h:%p"
727
+ # 2. Check and create environment config
728
+ self.print_status("Checking environment configuration...", None, 2)
729
+ success, current_config = self._check_and_create_environment_config(cloudx_env, current_config)
730
+ if not success:
731
+ return False
579
732
 
580
- base_config += f""" TCPKeepAlive yes
581
- ControlMaster auto
582
- ControlPath {control_path}
583
- ControlPersist 4h
584
-
585
- """
586
-
587
- # If file exists, append the new config, otherwise create it
588
- if self.ssh_config_file.exists():
589
- with open(self.ssh_config_file, 'a') as f:
590
- f.write("\n" + base_config)
591
- else:
592
- self.ssh_config_file.write_text(base_config)
593
- self.print_status("Base configuration created", True, 2)
733
+ # Write the updated config with generic and environment tiers
734
+ self.ssh_config_file.parent.mkdir(parents=True, exist_ok=True)
735
+ self.ssh_config_file.write_text(current_config)
736
+ self.print_status("Generic and environment configurations created", True, 2)
594
737
 
595
738
  # Set proper permissions on the config file
596
739
  if platform.system() != 'Windows':
597
740
  import stat
598
- self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions (owner read/write)
741
+ self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions
599
742
  self.print_status("Set config file permissions to 600", True, 2)
600
-
601
- # Add specific host entry using the consolidated helper method
602
- self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
603
- host_entry = self._build_host_config(cloudx_env, hostname, instance_id, include_proxy=False)
604
- with open(self.ssh_config_file, 'a') as f:
605
- f.write(host_entry)
606
- self.print_status("Host entry added", True, 2)
607
-
743
+
744
+ # 3. Add or update host entry (lowest level)
745
+ self.print_status(f"Adding/updating host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
746
+ if not self._add_host_entry(cloudx_env, instance_id, hostname, current_config):
747
+ return False
748
+
608
749
  # Handle system SSH config integration
609
750
  system_config_path = Path(self.home_dir) / ".ssh" / "config"
610
751
 
@@ -615,8 +756,17 @@ Host cloudx-{cloudx_env}-{host_pattern}
615
756
  self.print_status(f"Created SSH directory: {ssh_parent_dir}", True, 2)
616
757
  self._set_directory_permissions(ssh_parent_dir)
617
758
 
618
- # If our config file is the system config, we're done
619
- if self.ssh_config_file.samefile(system_config_path) if self.ssh_config_file.exists() and system_config_path.exists() else str(self.ssh_config_file) == str(system_config_path):
759
+ # Handle system config integration
760
+ same_file = False
761
+ if self.ssh_config_file.exists() and system_config_path.exists():
762
+ try:
763
+ same_file = self.ssh_config_file.samefile(system_config_path)
764
+ except:
765
+ same_file = str(self.ssh_config_file) == str(system_config_path)
766
+ else:
767
+ same_file = str(self.ssh_config_file) == str(system_config_path)
768
+
769
+ if same_file:
620
770
  self.print_status("Using system SSH config directly, no Include needed", True, 2)
621
771
  else:
622
772
  # Otherwise, make sure the system config includes our config file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudx-proxy
3
- Version: 0.4.2
3
+ Version: 0.4.4
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
@@ -112,6 +112,15 @@ uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
112
112
 
113
113
  # Setup with AWS environment
114
114
  uvx cloudx-proxy setup --aws-env prod
115
+
116
+ # Setup with custom SSH config location
117
+ uvx cloudx-proxy setup --ssh-config ~/.ssh/cloudx/config
118
+
119
+ # Setup with 1Password integration
120
+ uvx cloudx-proxy setup --1password
121
+
122
+ # Combine options
123
+ uvx cloudx-proxy setup --profile myprofile --ssh-key mykey --ssh-config ~/.ssh/cloudx/config --1password --aws-env prod
115
124
  ```
116
125
 
117
126
  The setup command will:
@@ -123,13 +132,16 @@ The setup command will:
123
132
 
124
133
  2. Manage SSH Keys:
125
134
  - Creates new SSH key pair if needed
126
- - Offers 1Password integration options:
127
- * Using 1Password SSH agent
128
- * Storing private key as 1Password document
135
+ - Fully supports 1Password integration:
136
+ * Using 1Password SSH agent via `--1password` flag
137
+ * Creates keys directly in 1Password's secure vault
138
+ * Only public keys are exported to the filesystem
139
+ * Follows SSH best practices using public keys to limit authentication attempts
129
140
 
130
141
  3. Configure SSH:
131
- - Creates ~/.ssh/vscode/config with proper settings
132
- - Sets up environment-specific configurations
142
+ - Creates SSH configs with proper settings (default: ~/.ssh/vscode/config)
143
+ - Custom config location can be specified with `--ssh-config`
144
+ - Sets up optimized environment-specific configurations
133
145
  - Configures ProxyCommand with all necessary parameters
134
146
  - Ensures main ~/.ssh/config includes the configuration
135
147
 
@@ -146,16 +158,30 @@ The setup command configures SSH to use cloudX-proxy as a ProxyCommand, enabling
146
158
  uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
147
159
  ```
148
160
 
149
- Will create a configuration like this:
161
+ Will create a three-tier configuration structure like this:
150
162
 
151
163
  ```
152
- # Base environment config (created once per environment)
153
- Host cloudx-dev-*
164
+ # Generic configuration (shared by all environments)
165
+ # Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
166
+ # Configuration type: generic
167
+ Host cloudx-*
154
168
  User ec2-user
169
+ TCPKeepAlive yes
170
+ ControlMaster auto
171
+ ControlPath ~/.ssh/control/%r@%h:%p
172
+ ControlPersist 4h
173
+
174
+ # Environment configuration (specific to a single environment)
175
+ # Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
176
+ # Configuration type: environment
177
+ Host cloudx-dev-*
155
178
  IdentityFile ~/.ssh/vscode/mykey
179
+ IdentitiesOnly yes
156
180
  ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
157
181
 
158
- # Host entry (added for specific instance)
182
+ # Host configuration (specific to a single instance)
183
+ # Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
184
+ # Configuration type: host
159
185
  Host cloudx-dev-myserver
160
186
  HostName i-0123456789abcdef0
161
187
  ```
@@ -173,21 +199,30 @@ In these examples, ssh will use cloudx-proxy to connect to AWS with the `myprofi
173
199
  VSCode will be able to connect to the instance using the same SSH configuration.
174
200
 
175
201
  ### SSH Configuration Details
176
- The setup command creates:
202
+ The setup command creates a hierarchical three-tier SSH configuration structure:
177
203
 
178
- 1. A base configuration for each environment (cloudx-{env}-*) with:
179
- - User and key settings
180
- - 1Password integration if selected
181
- - ProxyCommand with appropriate parameters
204
+ 1. Generic configuration (cloudx-*) containing common settings shared across all environments:
205
+ - User settings (ec2-user)
206
+ - TCP keepalive for connection stability
207
+ - SSH multiplexing for better performance (ControlMaster, ControlPath, ControlPersist)
182
208
 
183
- 2. Individual host entries for each instance:
184
- - Uses consistent naming (cloudx-{env}-hostname)
185
- - Maps to instance IDs automatically
186
- - Inherits environment-level settings
209
+ 2. Environment-specific configuration (cloudx-{env}-*) with:
210
+ - Authentication settings (IdentityFile, IdentityAgent for 1Password)
211
+ - ProxyCommand with environment-specific parameters
212
+ - Inherits all settings from the generic configuration
213
+
214
+ 3. Host-specific entries (cloudx-{env}-hostname) with:
215
+ - Instance ID (HostName directive)
216
+ - Inherits all settings from both generic and environment configurations
217
+
218
+ Each configuration tier is clearly marked with a timestamp and version information comment, making it easy to track when and how configurations were created.
187
219
 
188
220
  When adding new instances to an existing environment, you can choose to:
221
+ - Keep the existing environment configuration if it's compatible
189
222
  - Override the environment configuration with new settings
190
- - Add instance-specific settings while preserving the environment config
223
+ - Add host-specific settings only
224
+
225
+ This three-tier structure offers better maintainability by reducing duplication and making it clear which settings apply broadly and which are specific to an environment or host.
191
226
 
192
227
  ### VSCode Configuration
193
228
 
@@ -211,7 +246,9 @@ uvx cloudx-proxy setup [OPTIONS]
211
246
 
212
247
  Options:
213
248
  - `--profile` (default: vscode): AWS profile to use. The profile's IAM user should follow the format cloudX-{env}-{user}. The environment part will be used as the default environment during setup.
214
- - `--ssh-key` (default: vscode): Name of the SSH key to create/use. The key will be stored in ~/.ssh/vscode/{name}. This same name can be used in the connect command.
249
+ - `--ssh-key` (default: vscode): Name of the SSH key to create/use. The key will be stored in the SSH config directory. This same name can be used in the connect command.
250
+ - `--ssh-config` (optional): Path to the SSH config file to use. If specified, configuration and keys will be stored in this location. Default is ~/.ssh/vscode/config.
251
+ - `--1password` (flag): Enable 1Password SSH agent integration. Creates keys directly in 1Password and configures SSH to use the 1Password SSH agent.
215
252
  - `--aws-env` (optional): AWS environment directory to use. If specified, AWS configuration and credentials will be read from ~/.aws/aws-envs/{env}/.
216
253
 
217
254
  Example usage:
@@ -222,8 +259,11 @@ uvx cloudx-proxy setup
222
259
  # Setup with custom profile and key
223
260
  uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
224
261
 
225
- # Setup with AWS environment
226
- uvx cloudx-proxy setup --profile myprofile --aws-env prod
262
+ # Setup with custom SSH config and 1Password integration
263
+ uvx cloudx-proxy setup --ssh-config ~/.ssh/cloudx/config --1password
264
+
265
+ # Complete setup with all options
266
+ uvx cloudx-proxy setup --profile myprofile --ssh-key mykey --ssh-config ~/.ssh/cloudx/config --1password --aws-env prod
227
267
  ```
228
268
 
229
269
  #### Connect Command
@@ -238,6 +278,7 @@ Arguments:
238
278
  Options:
239
279
  - `--profile` (default: vscode): AWS profile to use. Should match the profile used in setup.
240
280
  - `--ssh-key` (default: vscode): Name of the SSH key to use. Should match the key name used in setup.
281
+ - `--ssh-config` (optional): Path to the SSH config file to use. If provided during setup, should match here.
241
282
  - `--region` (optional): AWS region to use. If not specified, uses the region from the AWS profile.
242
283
  - `--aws-env` (optional): AWS environment directory to use. Should match the environment used in setup.
243
284
 
@@ -303,12 +344,17 @@ These permissions are required to bootstrap the instance, so that after creation
303
344
  - Check that your AWS credentials have the required permissions
304
345
  - Verify the instance ID is correct
305
346
  - Increase the VSCode SSH timeout if needed
347
+ - Check if the instance is starting up (can take several minutes)
306
348
 
307
349
  3. **SSH Key Issues**
308
350
  - If using 1Password SSH agent, verify agent is running (~/.1password/agent.sock exists)
309
351
  - Check file permissions (600 for private key, 644 for public key)
310
352
  - Verify the public key is being successfully pushed to the instance
311
- - For stored keys in 1Password, ensure you can access them via the CLI
353
+ - For 1Password-managed keys, make sure:
354
+ * 1Password CLI is installed and authenticated (`op account list` works)
355
+ * SSH agent is enabled in 1Password settings
356
+ * Keys are added to the SSH agent in 1Password
357
+ * The key is visible with `op item list --categories "SSH Key"`
312
358
 
313
359
  4. **AWS Configuration**
314
360
  - Confirm AWS CLI is configured with valid credentials
@@ -0,0 +1,12 @@
1
+ cloudx_proxy/_1password.py,sha256=uxyCfVvO1eQrOfYRojst_LN2DV4fIwxM5moaQTn3wQY,5853
2
+ cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
3
+ cloudx_proxy/_version.py,sha256=rQpExgwkkSMYhPbtDdfhSejoe7mM9tgzyWoNno0mgIw,511
4
+ cloudx_proxy/cli.py,sha256=5IcfYFACUOa4pqSKuHucqZionI9P8n5ZLvtzyXYeTvw,4218
5
+ cloudx_proxy/core.py,sha256=RF3bX5MQiokRKjYEPnfWdKywGdtoVUvV2xZqm9uOl1g,8135
6
+ cloudx_proxy/setup.py,sha256=11DsAVt6L4d3VmuJNpU07-QdF3FwahOUpJyLDksMeaE,38216
7
+ cloudx_proxy-0.4.4.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
8
+ cloudx_proxy-0.4.4.dist-info/METADATA,sha256=22Yrnn7514Dd8eYte1c03xXpSjMhgATvwIftn1A95iQ,16757
9
+ cloudx_proxy-0.4.4.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
10
+ cloudx_proxy-0.4.4.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
11
+ cloudx_proxy-0.4.4.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
12
+ cloudx_proxy-0.4.4.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- cloudx_proxy/_1password.py,sha256=uxyCfVvO1eQrOfYRojst_LN2DV4fIwxM5moaQTn3wQY,5853
2
- cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
3
- cloudx_proxy/_version.py,sha256=_F8vLxUxrAtC2alXNPGVa9l3P6_vLpQAzemS6QlnPGQ,511
4
- cloudx_proxy/cli.py,sha256=kdrZydxL94BJrv6NnjIcceRqhoonBzMIx4vfm1Wl7qc,4104
5
- cloudx_proxy/core.py,sha256=RF3bX5MQiokRKjYEPnfWdKywGdtoVUvV2xZqm9uOl1g,8135
6
- cloudx_proxy/setup.py,sha256=jvv7ibJQ8svyjYYeVKwGa70L7RV2W7yS7JXEvKed3wI,33339
7
- cloudx_proxy-0.4.2.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
8
- cloudx_proxy-0.4.2.dist-info/METADATA,sha256=YAHtMfsqZ1aDk1FryZhgV-Q_vr4GcdBO_mzs-gbEpK8,14037
9
- cloudx_proxy-0.4.2.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
10
- cloudx_proxy-0.4.2.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
11
- cloudx_proxy-0.4.2.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
12
- cloudx_proxy-0.4.2.dist-info/RECORD,,