da9df57e11
encryptKeys and decryptKeys each took `vaultDir` and rebuilt `<vault>/keys` and `<vault>/tmp` from it, so `keysDir` and `tmpDir` in .keymanrc.json were honoured by main and list and silently ignored by the two operations that write. main was also passing vaultRoot where encrypt expected the keys directory, which put encrypted keys one level above where list looks for them: with any config at all, a key encrypted a second ago was invisible. Both now take keysDir and tmpDir explicitly. The decrypt location prompt names the real directories instead of the hardcoded `vault/tmp` and `~/.ssh`, which meant its labels were also its values — hence LOCAL_MODE. tests/vault-layout.test.ts is the regression: encrypt then list, driven through keyman() with only age and the prompts mocked, against a config using keysDir `encrypted` and tmpDir `plain`. Every unit suite passed through this bug because each was told which directory to use; the seam between them was untested. Verified it fails when main is reverted to pass vaultRoot.
Keyman - SSH Key Management with Age Encryption
Keyman is a simple command line tool built around the age encryption tool. It allows you to manage SSH keys in public GitHub repositories securely by encrypting the private keys.
Features
- 🔐 Encrypt SSH private keys with age encryption
- 📁 Organized vault structure:
vault/keys/for encrypted keys,vault/tmp/for decrypted keys - ⚙️ Configurable via
.keymanrc.jsonwith sensible defaults - 🔍 Interactive CLI for encrypting, decrypting, and listing keys
- 🔄 Support for key rotation
Quick Start
1. Generate Age Encryption Key
# Create vault structure
mkdir -p vault/keys vault/tmp
# Generate age encryption key (keep this secret!)
age-keygen -o vault/age.key
# Add to .gitignore
echo "vault/age.key" >> .gitignore
echo "vault/tmp/" >> .gitignore
2. Generate SSH Keys
# Generate SSH key pair
ssh-keygen -t ed25519 -f vault/tmp/id_deploy -N "" -C "deploy@myapp.dev"
3. Run Keyman
# Run keyman interactively
VAULT_ROOT=./vault keyman
# Or if you have .keymanrc.json configured, just run:
keyman
Configuration
Keyman uses sensible defaults but can be customized via .keymanrc.json:
{
"vaultRoot": "./vault",
"keysDir": "keys",
"tmpDir": "tmp",
"ageKeyFile": "age.key"
}
Configuration Priority
- VAULT_ROOT environment variable (highest priority)
- .keymanrc.json file (searched from current directory upward)
- Default values (lowest priority)
Default Values
vaultRoot:"vault"keysDir:"keys"tmpDir:"tmp"ageKeyFile:"age.key"
Vault Structure
project/
├── vault/
│ ├── age.key # Master encryption key (NEVER commit!)
│ ├── keys/ # Encrypted keys (safe to commit)
│ │ └── deploy/ # Each key has its own folder
│ │ ├── id_deploy.pub # Public key
│ │ └── id_deploy.age # Encrypted private key
│ └── tmp/ # Decrypted keys (NEVER commit!)
│ ├── id_deploy # Decrypted private key
│ └── id_deploy.pub # Public key
└── .keymanrc.json # Configuration (optional)
Operations
Keyman provides an interactive menu-driven interface with the following operations:
- 📋 List keys - Compact view showing all keys with checkbox indicators for their locations
- 🔒 Encrypt keys - Encrypt SSH keys from
vault/tmp/and store invault/keys/ - 🔓 Decrypt keys - Decrypt keys from
vault/keys/tovault/tmp/or~/.ssh/ - ❌ Quit - Exit the program
After completing any operation, keyman automatically returns to the main menu, allowing you to perform multiple operations in a single session without restarting the tool.
List Keys Output
The list command shows a compact, unified view of all SSH keys with their locations:
🔑 SSH Keys:
Key Name [Vault] [Tmp] [.ssh]
──────────────────────────────────────────────────────────
✅ id_deploy (.pub) [✓] [ ] [✓]
🔓 id_github (.pub) [✓] [✓] [ ]
🔒 id_backup (.pub) [✓] [ ] [ ]
⚠️ id_local (.pub) [ ] [ ] [✓]
Legend:
✅ = Managed (encrypted in vault + active in .ssh)
🔓 = Decrypted (in vault + decrypted to tmp)
🔒 = Encrypted only (in vault, not decrypted)
⚠️ = Unmanaged (in .ssh or tmp, not encrypted in vault)
Features:
- Public keys are indicated with
(.pub)suffix instead of separate entries - Status emoji shows management state at a glance
- Checkboxes
[✓]show presence in three locations:- [Vault] - Encrypted in vault/keys/
- [Tmp] - Decrypted in vault/tmp/
- [.ssh] - Active in ~/.ssh/
- Alphabetically sorted for easy scanning
- New 🔓 status for keys decrypted to tmp but not yet in .ssh
Example Usage
# Using environment variable
VAULT_ROOT=../../vault keyman
# Using default configuration
keyman
# Keyman will show:
# 📁 Vault Root: /path/to/vault
# 🔑 Keys Directory: /path/to/vault/keys
# 📂 Temp Directory: /path/to/vault/tmp
# 🔐 Age Key: /path/to/vault/age.key
Best Practices
- Never commit
vault/age.keyorvault/tmp/to version control - Always backup your
age.keysecurely (password manager, encrypted USB) - Commit
vault/keys/- encrypted keys are safe to share - Use environment variables for CI/CD:
VAULT_ROOT=/path/to/vault keyman - Keep .keymanrc.json in your project root for team consistency