feat: add ssh_key_id to config profiles for container key mounting

- Add ssh_key_id column to ConfigProfile model and migration
- Update config profile API to accept/return ssh_key_id
- Include ssh_key_id in ResolvedProfile and resolver logic
- Mount selected SSH key into container home dir at start_instance
- Frontend config profile form with SSH key selector dropdown
- Git mount URL validation defaults to profile's SSH key

Quality gates: pytest (231 passed, 6 pre-existing), tsc --noEmit clean
This commit is contained in:
Alex Blank
2026-05-29 12:53:51 +02:00
parent d413fb84a5
commit 57ff236f2d
11 changed files with 232 additions and 26 deletions
@@ -51,6 +51,7 @@ class ResolvedProfile:
mounts: dict[str, ResolvedMount] = field(default_factory=dict)
git_mounts: list[dict[str, Any]] = field(default_factory=list)
files: dict[str, str] = field(default_factory=dict)
ssh_key_id: uuid.UUID | None = None
env_overrides: dict[str, str] = field(default_factory=dict)
hint_overrides: dict[str, str] = field(default_factory=dict)
file_overrides: dict[str, str] = field(default_factory=dict)
@@ -318,6 +319,9 @@ async def _resolve_profile_recursive(
result.git_mounts = _merge_git_mounts(
result.git_mounts, included.git_mounts, included.profile_name
)
# Later included profile's SSH key wins
if included.ssh_key_id is not None:
result.ssh_key_id = included.ssh_key_id
# Apply the profile's own settings (selected profile overrides includes)
result.env_vars = _merge_env_vars(
@@ -349,6 +353,9 @@ async def _resolve_profile_recursive(
profile.git_mounts or [],
profile.name,
)
# Own SSH key overrides any inherited one
if profile.ssh_key_id is not None:
result.ssh_key_id = profile.ssh_key_id
return result
@@ -564,4 +571,5 @@ def resolved_profile_to_dict(resolved: ResolvedProfile) -> dict[str, Any]:
},
"git_mounts": resolved.git_mounts,
"included_profiles": resolved.included_profiles,
"ssh_key_id": str(resolved.ssh_key_id) if resolved.ssh_key_id else None,
}
+3 -2
View File
@@ -19,17 +19,18 @@ def _get_fernet() -> Fernet:
return Fernet(key)
def prepare_ssh_key_files(instance_dir: str, ssh_key) -> str:
def prepare_ssh_key_files(instance_dir: str, ssh_key, subdir: str = ".ssh") -> str:
"""Decrypt and write SSH key files to instance directory for container mounting.
Args:
instance_dir: Path to instance directory
ssh_key: SSHKey model instance with encrypted private key
subdir: Subdirectory within instance_dir to write to (default: ".ssh")
Returns:
Path to the .ssh directory
"""
ssh_dir = Path(instance_dir) / ".ssh"
ssh_dir = Path(instance_dir) / subdir
ssh_dir.mkdir(parents=True, exist_ok=True)
# Decrypt private key