Problem Statement
Currently, git-supervisor relies on the host machine's ~/.ssh directory for SSH authentication. The ssh_identity_file config field takes an absolute or ~-relative path to a private key that must already exist on the filesystem. This creates an external dependency: the CLI cannot function correctly unless the operator has pre-configured SSH keys in the right locations. This makes the tool harder to set up, less portable, and couples its configuration to the host's SSH setup.
Solution
Introduce a managed key directory (~/.config/git-supervisor/keys/) that git-supervisor owns. A new config field ssh_key_name on each host allows referencing a key by bare filename. At runtime, the CLI resolves the key by searching the managed directory first, then falling back to ~/.ssh/. This gives operators a self-contained way to organize deployment keys without depending on the system SSH config.
Users place key files manually into ~/.config/git-supervisor/keys/. No import/export/generation commands are needed.
User Stories
- As a DevOps engineer, I want to place a deployment key in
~/.config/git-supervisor/keys/deploy_key and reference it as ssh_key_name: deploy_key in my config, so that my SSH keys are organized separately from my personal SSH config.
- As a DevOps engineer, I want the CLI to search
~/.config/git-supervisor/keys/ first and then fall back to ~/.ssh/, so that I can use existing keys in ~/.ssh/ without copying them.
- As a DevOps engineer, I want the CLI to error clearly if a referenced key name is not found in either search directory, so that I can quickly diagnose misconfiguration.
- As a DevOps engineer, I want the CLI to warn or error if a key file has overly permissive permissions (not
0600 or 0400), so that I don't accidentally use insecure keys.
- As a DevOps engineer, I want
ssh_key_name to take precedence over ssh_identity_file when both are set, so that the managed approach is preferred but backward compatibility is preserved.
- As a DevOps engineer, I want the existing
ssh_identity_file field to continue working exactly as before, so that my current deployments are not broken.
- As a DevOps engineer, I want to use
ssh_key_name with any host (remote or localhost), so that the key resolution is consistent regardless of target.
- As a DevOps engineer deploying via Docker, I want the Docker volume-mount approach (
./keys:/root/.ssh) to continue working unchanged, so that containerized deployments are not affected.
- As a DevOps engineer, I want
ssh_key_name to be a bare filename (not a path), so that configs are portable across machines with different home directories.
- As a DevOps engineer, I want a clear sample config showing the new
ssh_key_name field alongside the existing ssh_identity_file, so that I understand both options.
Implementation Decisions
- New config field: Add
ssh_key_name: Option<String> to the Host struct in the config module. This is a bare filename, not a path.
- Key resolution module: Create a new
keys module responsible for resolving a key name to an absolute path. Search order: ~/.config/git-supervisor/keys/<name> → ~/.ssh/<name>. Return error if not found in either location.
- Permission validation: The key resolver checks Unix file permissions on the resolved key file. If permissions are more permissive than
0600, emit an error. This mirrors OpenSSH's own behavior of rejecting overly-permissive keys.
- Precedence: When both
ssh_key_name and ssh_identity_file are set on a host, ssh_key_name wins. The resolved path from the key resolver is passed to ssh -i, exactly as ssh_identity_file paths are today.
- SSH module changes: Before building the
ssh command, the SSH module resolves the effective identity file: call the key resolver if ssh_key_name is set, otherwise use ssh_identity_file if present, otherwise no -i flag.
- No new CLI subcommands: Users manage keys by placing files directly in
~/.config/git-supervisor/keys/. No key import, key list, key generate, or similar commands.
- No Docker changes: The Docker Compose setup and Dockerfile remain unchanged.
- Sample config update: Update
deployments.sample.yaml to show ssh_key_name usage alongside the existing ssh_identity_file example.
Testing Decisions
A good test verifies external behavior (given this input, expect this output/error) without coupling to internal implementation details like struct field names or private function signatures.
Modules to test:
-
Key resolution module (keys.rs) — the core new logic:
- Resolves a key name found in the managed directory (
~/.config/git-supervisor/keys/)
- Falls back to
~/.ssh/ when not in managed directory
- Errors when key is not found in either location
- Errors when key file has overly permissive permissions
- Uses temp directories in tests to avoid depending on the real filesystem
-
Config module (config.rs) — extend existing tests:
- Parses
ssh_key_name field from YAML
- Both
ssh_key_name and ssh_identity_file can coexist in YAML
Prior art: existing unit tests in config.rs (e.g., parse_minimal_yaml, host_release_count_parsed) and ssh.rs (e.g., local_target_detection_supports_common_localhost_forms) follow the same pattern of inline YAML parsing and assertions.
Out of Scope
- SSH key generation (
ssh-keygen wrapper)
- Key import/export CLI subcommands
- Encryption or passphrase-protected key support
- Docker deployment changes
- SSH agent forwarding or agent-based authentication
- Configurable search paths (the two directories are hardcoded)
- Windows support (permission checks use Unix file modes)
Further Notes
- The managed key directory
~/.config/git-supervisor/keys/ follows the XDG Base Directory convention ($XDG_CONFIG_HOME/git-supervisor/keys/). A future enhancement could respect $XDG_CONFIG_HOME if set, but for now ~/.config is hardcoded.
- The permission check is Unix-only. If cross-platform support becomes a concern, the check can be conditionally compiled.
Problem Statement
Currently,
git-supervisorrelies on the host machine's~/.sshdirectory for SSH authentication. Thessh_identity_fileconfig field takes an absolute or~-relative path to a private key that must already exist on the filesystem. This creates an external dependency: the CLI cannot function correctly unless the operator has pre-configured SSH keys in the right locations. This makes the tool harder to set up, less portable, and couples its configuration to the host's SSH setup.Solution
Introduce a managed key directory (
~/.config/git-supervisor/keys/) thatgit-supervisorowns. A new config fieldssh_key_nameon each host allows referencing a key by bare filename. At runtime, the CLI resolves the key by searching the managed directory first, then falling back to~/.ssh/. This gives operators a self-contained way to organize deployment keys without depending on the system SSH config.Users place key files manually into
~/.config/git-supervisor/keys/. No import/export/generation commands are needed.User Stories
~/.config/git-supervisor/keys/deploy_keyand reference it asssh_key_name: deploy_keyin my config, so that my SSH keys are organized separately from my personal SSH config.~/.config/git-supervisor/keys/first and then fall back to~/.ssh/, so that I can use existing keys in~/.ssh/without copying them.0600or0400), so that I don't accidentally use insecure keys.ssh_key_nameto take precedence overssh_identity_filewhen both are set, so that the managed approach is preferred but backward compatibility is preserved.ssh_identity_filefield to continue working exactly as before, so that my current deployments are not broken.ssh_key_namewith any host (remote or localhost), so that the key resolution is consistent regardless of target../keys:/root/.ssh) to continue working unchanged, so that containerized deployments are not affected.ssh_key_nameto be a bare filename (not a path), so that configs are portable across machines with different home directories.ssh_key_namefield alongside the existingssh_identity_file, so that I understand both options.Implementation Decisions
ssh_key_name: Option<String>to theHoststruct in the config module. This is a bare filename, not a path.keysmodule responsible for resolving a key name to an absolute path. Search order:~/.config/git-supervisor/keys/<name>→~/.ssh/<name>. Return error if not found in either location.0600, emit an error. This mirrors OpenSSH's own behavior of rejecting overly-permissive keys.ssh_key_nameandssh_identity_fileare set on a host,ssh_key_namewins. The resolved path from the key resolver is passed tossh -i, exactly asssh_identity_filepaths are today.sshcommand, the SSH module resolves the effective identity file: call the key resolver ifssh_key_nameis set, otherwise usessh_identity_fileif present, otherwise no-iflag.~/.config/git-supervisor/keys/. Nokey import,key list,key generate, or similar commands.deployments.sample.yamlto showssh_key_nameusage alongside the existingssh_identity_fileexample.Testing Decisions
A good test verifies external behavior (given this input, expect this output/error) without coupling to internal implementation details like struct field names or private function signatures.
Modules to test:
Key resolution module (
keys.rs) — the core new logic:~/.config/git-supervisor/keys/)~/.ssh/when not in managed directoryConfig module (
config.rs) — extend existing tests:ssh_key_namefield from YAMLssh_key_nameandssh_identity_filecan coexist in YAMLPrior art: existing unit tests in
config.rs(e.g.,parse_minimal_yaml,host_release_count_parsed) andssh.rs(e.g.,local_target_detection_supports_common_localhost_forms) follow the same pattern of inline YAML parsing and assertions.Out of Scope
ssh-keygenwrapper)Further Notes
~/.config/git-supervisor/keys/follows the XDG Base Directory convention ($XDG_CONFIG_HOME/git-supervisor/keys/). A future enhancement could respect$XDG_CONFIG_HOMEif set, but for now~/.configis hardcoded.