063a839790
- Add clone_mode and branch fields to tool_instances - Add ssh_key_id to git_repositories for per-repo SSH key assignment - Implement host-side git cloning with branch selection (default: main) - Mount SSH keys into containers for git operations in clone mode - Add dirty state check on clone-mode instance deletion with confirmation - Update SessionsPage with mount/clone selector, branch input, SSH key display - Add SSH key selector to repository creation form - Add dirty delete confirmation modal with changed files list - Update API schemas and endpoints for new fields - Sync delta specs to main specs (git-repo, tool-instances, repo-clone-mode) - Archive completed OpenSpec change: repo-clone-mode-with-ssh - Document git requirement for custom tool types Quality gates: Frontend typecheck and build passed OpenSpec: repo-clone-mode-with-ssh archived with all tasks complete
74 lines
2.0 KiB
Python
74 lines
2.0 KiB
Python
"""SSH key service utilities for preparing keys for container use."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from cryptography.fernet import Fernet
|
|
|
|
from src.config import Settings
|
|
|
|
|
|
def _get_fernet() -> Fernet:
|
|
"""Generate a valid Fernet key from the session secret."""
|
|
import base64
|
|
import hashlib
|
|
|
|
settings = Settings()
|
|
key_bytes = hashlib.sha256(settings.session_secret.encode()).digest()
|
|
key = base64.urlsafe_b64encode(key_bytes)
|
|
return Fernet(key)
|
|
|
|
|
|
def prepare_ssh_key_files(instance_dir: str, ssh_key) -> 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
|
|
|
|
Returns:
|
|
Path to the .ssh directory
|
|
"""
|
|
ssh_dir = Path(instance_dir) / ".ssh"
|
|
ssh_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Decrypt private key
|
|
fernet = _get_fernet()
|
|
private_key = fernet.decrypt(ssh_key.private_key_encrypted.encode()).decode()
|
|
|
|
# Write private key with restricted permissions
|
|
private_key_path = ssh_dir / "id_ed25519"
|
|
private_key_path.write_text(private_key)
|
|
os.chmod(private_key_path, 0o600)
|
|
|
|
# Write public key
|
|
public_key_path = ssh_dir / "id_ed25519.pub"
|
|
public_key_path.write_text(ssh_key.public_key)
|
|
os.chmod(public_key_path, 0o644)
|
|
|
|
# Write SSH config
|
|
config_path = ssh_dir / "config"
|
|
config_content = """Host *
|
|
StrictHostKeyChecking no
|
|
UserKnownHostsFile /dev/null
|
|
IdentityFile ~/.ssh/id_ed25519
|
|
IdentitiesOnly yes
|
|
"""
|
|
config_path.write_text(config_content)
|
|
os.chmod(config_path, 0o644)
|
|
|
|
return str(ssh_dir)
|
|
|
|
|
|
def cleanup_ssh_key_files(instance_dir: str) -> None:
|
|
"""Remove temporary SSH key files from instance directory.
|
|
|
|
Args:
|
|
instance_dir: Path to instance directory
|
|
"""
|
|
ssh_dir = Path(instance_dir) / ".ssh"
|
|
if ssh_dir.exists():
|
|
for file_path in ssh_dir.iterdir():
|
|
file_path.unlink()
|
|
ssh_dir.rmdir()
|