cloudx-proxy 0.4.3__tar.gz → 0.4.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.
Files changed (23) hide show
  1. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/CHANGELOG.md +7 -0
  2. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/PKG-INFO +35 -20
  3. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/README.md +34 -19
  4. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/_version.py +2 -2
  5. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/cli.py +2 -0
  6. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/setup.py +265 -122
  7. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/PKG-INFO +35 -20
  8. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/.github/workflows/release.yml +0 -0
  9. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/.gitignore +0 -0
  10. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/.releaserc +0 -0
  11. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/CONTRIBUTING.md +0 -0
  12. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/LICENSE +0 -0
  13. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/_1password.py +0 -0
  14. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/__init__.py +0 -0
  15. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/core.py +0 -0
  16. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/SOURCES.txt +0 -0
  17. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/dependency_links.txt +0 -0
  18. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/entry_points.txt +0 -0
  19. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/requires.txt +0 -0
  20. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/top_level.txt +0 -0
  21. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/package.json +0 -0
  22. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/pyproject.toml +0 -0
  23. {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/setup.cfg +0 -0
@@ -1,3 +1,10 @@
1
+ ## [0.4.4](https://github.com/easytocloud/cloudX-proxy/compare/v0.4.3...v0.4.4) (2025-03-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * improved generated ssh config file for multiple cloudx instances ([11c28d1](https://github.com/easytocloud/cloudX-proxy/commit/11c28d1534bddfbbd7d108a2a961aa21166e899a))
7
+
1
8
  ## [0.4.3](https://github.com/easytocloud/cloudX-proxy/compare/v0.4.2...v0.4.3) (2025-03-06)
2
9
 
3
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudx-proxy
3
- Version: 0.4.3
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
@@ -158,22 +158,30 @@ The setup command configures SSH to use cloudX-proxy as a ProxyCommand, enabling
158
158
  uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
159
159
  ```
160
160
 
161
- Will create a configuration like this:
161
+ Will create a three-tier configuration structure like this:
162
162
 
163
163
  ```
164
- # Base environment config (created once per environment)
165
- # Environment-wide configuration
166
- 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-*
167
168
  User ec2-user
168
- IdentityFile ~/.ssh/vscode/mykey
169
- IdentitiesOnly yes
170
- ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
171
169
  TCPKeepAlive yes
172
170
  ControlMaster auto
173
171
  ControlPath ~/.ssh/control/%r@%h:%p
174
172
  ControlPersist 4h
175
173
 
176
- # Minimal host entry (inherits all settings from environment config)
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-*
178
+ IdentityFile ~/.ssh/vscode/mykey
179
+ IdentitiesOnly yes
180
+ ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
181
+
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
177
185
  Host cloudx-dev-myserver
178
186
  HostName i-0123456789abcdef0
179
187
  ```
@@ -191,23 +199,30 @@ In these examples, ssh will use cloudx-proxy to connect to AWS with the `myprofi
191
199
  VSCode will be able to connect to the instance using the same SSH configuration.
192
200
 
193
201
  ### SSH Configuration Details
194
- The setup command creates an optimized SSH configuration structure:
202
+ The setup command creates a hierarchical three-tier SSH configuration structure:
195
203
 
196
- 1. A base configuration for each environment (cloudx-{env}-*) with:
197
- - User and key settings
198
- - 1Password SSH agent integration if selected
199
- - ProxyCommand with appropriate parameters
200
- - SSH multiplexing for better performance
204
+ 1. Generic configuration (cloudx-*) containing common settings shared across all environments:
205
+ - User settings (ec2-user)
201
206
  - TCP keepalive for connection stability
207
+ - SSH multiplexing for better performance (ControlMaster, ControlPath, ControlPersist)
202
208
 
203
- 2. Minimal host entries for each instance:
204
- - Uses consistent naming (cloudx-{env}-hostname)
205
- - Only contains the HostName directive for the instance ID
206
- - Inherits all environment-level settings automatically
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.
207
219
 
208
220
  When adding new instances to an existing environment, you can choose to:
221
+ - Keep the existing environment configuration if it's compatible
209
222
  - Override the environment configuration with new settings
210
- - 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.
211
226
 
212
227
  ### VSCode Configuration
213
228
 
@@ -108,22 +108,30 @@ The setup command configures SSH to use cloudX-proxy as a ProxyCommand, enabling
108
108
  uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
109
109
  ```
110
110
 
111
- Will create a configuration like this:
111
+ Will create a three-tier configuration structure like this:
112
112
 
113
113
  ```
114
- # Base environment config (created once per environment)
115
- # Environment-wide configuration
116
- Host cloudx-dev-*
114
+ # Generic configuration (shared by all environments)
115
+ # Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
116
+ # Configuration type: generic
117
+ Host cloudx-*
117
118
  User ec2-user
118
- IdentityFile ~/.ssh/vscode/mykey
119
- IdentitiesOnly yes
120
- ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
121
119
  TCPKeepAlive yes
122
120
  ControlMaster auto
123
121
  ControlPath ~/.ssh/control/%r@%h:%p
124
122
  ControlPersist 4h
125
123
 
126
- # Minimal host entry (inherits all settings from environment config)
124
+ # Environment configuration (specific to a single environment)
125
+ # Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
126
+ # Configuration type: environment
127
+ Host cloudx-dev-*
128
+ IdentityFile ~/.ssh/vscode/mykey
129
+ IdentitiesOnly yes
130
+ ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
131
+
132
+ # Host configuration (specific to a single instance)
133
+ # Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
134
+ # Configuration type: host
127
135
  Host cloudx-dev-myserver
128
136
  HostName i-0123456789abcdef0
129
137
  ```
@@ -141,23 +149,30 @@ In these examples, ssh will use cloudx-proxy to connect to AWS with the `myprofi
141
149
  VSCode will be able to connect to the instance using the same SSH configuration.
142
150
 
143
151
  ### SSH Configuration Details
144
- The setup command creates an optimized SSH configuration structure:
152
+ The setup command creates a hierarchical three-tier SSH configuration structure:
145
153
 
146
- 1. A base configuration for each environment (cloudx-{env}-*) with:
147
- - User and key settings
148
- - 1Password SSH agent integration if selected
149
- - ProxyCommand with appropriate parameters
150
- - SSH multiplexing for better performance
154
+ 1. Generic configuration (cloudx-*) containing common settings shared across all environments:
155
+ - User settings (ec2-user)
151
156
  - TCP keepalive for connection stability
157
+ - SSH multiplexing for better performance (ControlMaster, ControlPath, ControlPersist)
152
158
 
153
- 2. Minimal host entries for each instance:
154
- - Uses consistent naming (cloudx-{env}-hostname)
155
- - Only contains the HostName directive for the instance ID
156
- - Inherits all environment-level settings automatically
159
+ 2. Environment-specific configuration (cloudx-{env}-*) with:
160
+ - Authentication settings (IdentityFile, IdentityAgent for 1Password)
161
+ - ProxyCommand with environment-specific parameters
162
+ - Inherits all settings from the generic configuration
163
+
164
+ 3. Host-specific entries (cloudx-{env}-hostname) with:
165
+ - Instance ID (HostName directive)
166
+ - Inherits all settings from both generic and environment configurations
167
+
168
+ Each configuration tier is clearly marked with a timestamp and version information comment, making it easy to track when and how configurations were created.
157
169
 
158
170
  When adding new instances to an existing environment, you can choose to:
171
+ - Keep the existing environment configuration if it's compatible
159
172
  - Override the environment configuration with new settings
160
- - Add instance-specific settings while preserving the environment config
173
+ - Add host-specific settings only
174
+
175
+ 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.
161
176
 
162
177
  ### VSCode Configuration
163
178
 
@@ -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.3'
21
- __version_tuple__ = version_tuple = (0, 4, 3)
20
+ __version__ = version = '0.4.4'
21
+ __version_tuple__ = version_tuple = (0, 4, 4)
@@ -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
 
@@ -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,24 +395,31 @@ class CloudXSetup:
383
395
  IdentitiesOnly yes
384
396
  """
385
397
 
386
- def _build_environment_config(self, cloudx_env: str) -> str:
387
- """Build an environment-wide configuration block with all common settings.
398
+ def _get_timestamp(self) -> str:
399
+ """Get a formatted timestamp for configuration comments.
388
400
 
389
- Args:
390
- cloudx_env: CloudX environment
391
-
392
401
  Returns:
393
- str: Complete environment configuration block
402
+ str: Formatted timestamp
394
403
  """
395
- host_entry = f"""
396
- Host cloudx-{cloudx_env}-*
397
- User ec2-user
398
- """
399
- # Add authentication configuration
400
- host_entry += self._build_auth_config()
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.
401
409
 
402
- # Add ProxyCommand
403
- host_entry += f""" ProxyCommand {self._build_proxy_command()}
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
404
423
  """
405
424
 
406
425
  # Add SSH multiplexing configuration
@@ -409,16 +428,42 @@ Host cloudx-{cloudx_env}-*
409
428
  # Use forward slashes for Windows as well, SSH client will handle conversion
410
429
  control_path = "~/.ssh/control/%r@%h:%p"
411
430
 
412
- host_entry += f""" TCPKeepAlive yes
413
- ControlMaster auto
431
+ config += f""" ControlMaster auto
414
432
  ControlPath {control_path}
415
433
  ControlPersist 4h
416
434
  """
417
435
 
418
- return host_entry
436
+ return config
437
+
438
+ def _build_environment_config(self, cloudx_env: str) -> str:
439
+ """Build an environment-specific configuration block.
440
+
441
+ Args:
442
+ cloudx_env: CloudX environment
443
+
444
+ Returns:
445
+ str: Environment configuration block
446
+ """
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}-*
455
+ """
456
+ # Add authentication configuration
457
+ config += self._build_auth_config()
458
+
459
+ # Add ProxyCommand
460
+ config += f""" ProxyCommand {self._build_proxy_command()}
461
+ """
462
+
463
+ return config
419
464
 
420
465
  def _build_host_config(self, cloudx_env: str, hostname: str, instance_id: str) -> str:
421
- """Build a minimal host configuration block that inherits from the environment.
466
+ """Build a host-specific configuration block.
422
467
 
423
468
  Args:
424
469
  cloudx_env: CloudX environment
@@ -426,13 +471,62 @@ Host cloudx-{cloudx_env}-*
426
471
  instance_id: EC2 instance ID
427
472
 
428
473
  Returns:
429
- str: Minimal host configuration block with only hostname
474
+ str: Host configuration block
430
475
  """
431
- return f"""
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
432
483
  Host cloudx-{cloudx_env}-{hostname}
433
484
  HostName {instance_id}
434
485
  """
435
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
+
436
530
  def _add_host_entry(self, cloudx_env: str, instance_id: str, hostname: str, current_config: str) -> bool:
437
531
  """Add settings to a specific host entry.
438
532
 
@@ -446,13 +540,29 @@ Host cloudx-{cloudx_env}-{hostname}
446
540
  bool: True if settings were added successfully
447
541
  """
448
542
  try:
449
- # Generate the host entry using the consolidated helper method
450
- host_entry = self._build_host_config(cloudx_env, hostname, instance_id)
451
-
452
- # Append host entry
453
- with open(self.ssh_config_file, 'a') as f:
454
- f.write(host_entry)
455
- 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)
456
566
 
457
567
  # Set proper permissions on the config file
458
568
  if platform.system() != 'Windows':
@@ -469,6 +579,81 @@ Host cloudx-{cloudx_env}-{hostname}
469
579
  self.print_status("Continuing setup despite SSH config issues", None, 2)
470
580
  return True
471
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
472
657
 
473
658
  def _ensure_control_dir(self) -> bool:
474
659
  """Create SSH control directory with proper permissions.
@@ -496,38 +681,21 @@ Host cloudx-{cloudx_env}-{hostname}
496
681
  return False
497
682
 
498
683
  def setup_ssh_config(self, cloudx_env: str, instance_id: str, hostname: str) -> bool:
499
- """Set up SSH config for the instance.
500
-
501
- This method manages the SSH configuration in ~/.ssh/vscode/config, with the following behavior:
502
- 1. For a new environment (if cloudx-{env}-* doesn't exist):
503
- Creates a base config with:
504
- - User and key configuration
505
- - 1Password SSH agent integration if selected
506
- - ProxyCommand using uvx cloudx-proxy with proper parameters
507
- - SSH multiplexing configuration (ControlMaster, ControlPath, ControlPersist)
508
-
509
- 2. For an existing environment:
510
- - Skips creating duplicate environment config
511
- - Only adds the new host entry
512
-
513
- Example config structure:
514
- ```
515
- # Base environment config (created only once per environment)
516
- Host cloudx-{env}-*
517
- User ec2-user
518
- IdentityAgent ~/.1password/agent.sock # If using 1Password
519
- IdentityFile ~/.ssh/vscode/key.pub # .pub for 1Password, no .pub otherwise
520
- IdentitiesOnly yes # If using 1Password
521
- TCPKeepAlive yes
522
- ControlMaster auto
523
- ControlPath ~/.ssh/control/%r@%h:%p
524
- ControlPersist 4h
525
- ProxyCommand uvx cloudx-proxy connect %h %p --profile profile --aws-env env
526
-
527
- # Host entries (added for each instance)
528
- Host cloudx-{env}-hostname
529
- HostName i-1234567890
530
- ```
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
531
699
 
532
700
  Args:
533
701
  cloudx_env: CloudX environment (e.g., dev, prod)
@@ -538,80 +706,46 @@ Host cloudx-{cloudx_env}-{hostname}
538
706
  bool: True if config was set up successfully
539
707
  """
540
708
  self.print_header("SSH Configuration")
541
- self.print_status("Setting up SSH configuration...")
709
+ self.print_status("Setting up SSH configuration with three-tier approach...")
542
710
 
543
711
  try:
544
- # Check existing configuration
545
- if self.ssh_config_file.exists():
546
- current_config = self.ssh_config_file.read_text()
547
- # Check if configuration for this environment already exists
548
- if f"Host cloudx-{cloudx_env}-*" in current_config:
549
- self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
550
- choice = self.prompt(
551
- "Would you like to \n"
552
- " 1: override the existing config\n"
553
- " 2: add settings to the specific host entry?\n"
554
- "Select an option",
555
- "1"
556
- )
557
- if choice == "2":
558
- # Add settings to specific host entry
559
- self.print_status("Adding settings to specific host entry", None, 2)
560
- return self._add_host_entry(cloudx_env, instance_id, hostname, current_config)
561
- else:
562
- # Remove existing config for this environment
563
- self.print_status("Removing existing configuration", None, 2)
564
- lines = current_config.splitlines()
565
- new_lines = []
566
- skip = False
567
- for line in lines:
568
- if line.strip() == f"Host cloudx-{cloudx_env}-*":
569
- skip = True
570
- elif skip and line.startswith("Host "):
571
- skip = False
572
- if not skip:
573
- new_lines.append(line)
574
- current_config = "\n".join(new_lines)
575
- with open(self.ssh_config_file, 'w') as f:
576
- f.write(current_config)
577
-
578
- # Create base config
579
- self.print_status(f"Creating new config for cloudx-{cloudx_env}-*", None, 2)
580
-
581
712
  # Ensure control directory exists with proper permissions
582
713
  if not self._ensure_control_dir():
583
714
  return False
584
715
 
585
- # Build base configuration with wildcard hostname pattern
586
- # Start with a header comment
587
- base_config = """# cloudx-proxy SSH Configuration
588
- # Environment configuration with settings applied to all hosts in this environment
589
- """
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()
590
720
 
591
- # Add environment-wide configuration with all common settings
592
- base_config += self._build_environment_config(cloudx_env)
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
593
726
 
594
- # If file exists, append the new config, otherwise create it
595
- if self.ssh_config_file.exists():
596
- with open(self.ssh_config_file, 'a') as f:
597
- f.write("\n" + base_config)
598
- else:
599
- self.ssh_config_file.write_text(base_config)
600
- self.print_status("Base configuration created", True, 2)
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
732
+
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)
601
737
 
602
738
  # Set proper permissions on the config file
603
739
  if platform.system() != 'Windows':
604
740
  import stat
605
- 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
606
742
  self.print_status("Set config file permissions to 600", True, 2)
607
-
608
- # Add specific host entry - only specifying the hostname
609
- self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
610
- host_entry = self._build_host_config(cloudx_env, hostname, instance_id)
611
- with open(self.ssh_config_file, 'a') as f:
612
- f.write(host_entry)
613
- self.print_status("Host entry added", True, 2)
614
-
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
+
615
749
  # Handle system SSH config integration
616
750
  system_config_path = Path(self.home_dir) / ".ssh" / "config"
617
751
 
@@ -622,8 +756,17 @@ Host cloudx-{cloudx_env}-{hostname}
622
756
  self.print_status(f"Created SSH directory: {ssh_parent_dir}", True, 2)
623
757
  self._set_directory_permissions(ssh_parent_dir)
624
758
 
625
- # If our config file is the system config, we're done
626
- 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:
627
770
  self.print_status("Using system SSH config directly, no Include needed", True, 2)
628
771
  else:
629
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.3
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
@@ -158,22 +158,30 @@ The setup command configures SSH to use cloudX-proxy as a ProxyCommand, enabling
158
158
  uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
159
159
  ```
160
160
 
161
- Will create a configuration like this:
161
+ Will create a three-tier configuration structure like this:
162
162
 
163
163
  ```
164
- # Base environment config (created once per environment)
165
- # Environment-wide configuration
166
- 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-*
167
168
  User ec2-user
168
- IdentityFile ~/.ssh/vscode/mykey
169
- IdentitiesOnly yes
170
- ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
171
169
  TCPKeepAlive yes
172
170
  ControlMaster auto
173
171
  ControlPath ~/.ssh/control/%r@%h:%p
174
172
  ControlPersist 4h
175
173
 
176
- # Minimal host entry (inherits all settings from environment config)
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-*
178
+ IdentityFile ~/.ssh/vscode/mykey
179
+ IdentitiesOnly yes
180
+ ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
181
+
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
177
185
  Host cloudx-dev-myserver
178
186
  HostName i-0123456789abcdef0
179
187
  ```
@@ -191,23 +199,30 @@ In these examples, ssh will use cloudx-proxy to connect to AWS with the `myprofi
191
199
  VSCode will be able to connect to the instance using the same SSH configuration.
192
200
 
193
201
  ### SSH Configuration Details
194
- The setup command creates an optimized SSH configuration structure:
202
+ The setup command creates a hierarchical three-tier SSH configuration structure:
195
203
 
196
- 1. A base configuration for each environment (cloudx-{env}-*) with:
197
- - User and key settings
198
- - 1Password SSH agent integration if selected
199
- - ProxyCommand with appropriate parameters
200
- - SSH multiplexing for better performance
204
+ 1. Generic configuration (cloudx-*) containing common settings shared across all environments:
205
+ - User settings (ec2-user)
201
206
  - TCP keepalive for connection stability
207
+ - SSH multiplexing for better performance (ControlMaster, ControlPath, ControlPersist)
202
208
 
203
- 2. Minimal host entries for each instance:
204
- - Uses consistent naming (cloudx-{env}-hostname)
205
- - Only contains the HostName directive for the instance ID
206
- - Inherits all environment-level settings automatically
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.
207
219
 
208
220
  When adding new instances to an existing environment, you can choose to:
221
+ - Keep the existing environment configuration if it's compatible
209
222
  - Override the environment configuration with new settings
210
- - 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.
211
226
 
212
227
  ### VSCode Configuration
213
228
 
File without changes
File without changes
File without changes
File without changes
File without changes