7.0 KiB
sshkeeper v0.4.0 — Model & Workflow Cleanup
v0.4.0 is a structural release. The main goal is to make sshkeeper's data model match what the user sees: server profiles are real reusable objects, routes reference those objects instead of mutable text, and the TUI offers pickers for things that already exist instead of asking you to retype them.
Existing databases and vaults are migrated automatically. No manual conversion is required.
Routes are now real relationships
The old implementation stored both ProxyJump text and a second route
representation. Different commands wrote different fields, and runtime SSH used
one in preference to the other. That made it possible for edit --proxy-jump to
say "Saved" while the old structured route was still used.
v0.4.0 makes Route canonical and stores profile hops in a normalized
server_route_hops table with foreign keys to stable server IDs.
- Renaming a bastion no longer breaks dependent routes.
- Deleting a profile that is still used as a route hop is rejected and names the dependent profiles.
- Self references, duplicate hops and route cycles are rejected.
proxy_jumpand legacyroute_hopsremain compatibility projections for old databases/tools, not competing sources of truth.- Existing
group_namestrings are likewise migrated to first-classgroupswith stable IDs.
Profile hops no longer depend on ~/.ssh/config
A route hop that references a sshkeeper profile now resolves that profile's real
host, user, port and identity file from SQLite. sshkeeper generates a temporary
OpenSSH config for the connection and passes it with ssh -F.
This fixes the old accidental requirement that a sshkeeper alias such as
bastion-prod also had to exist as a matching Host in the user's OpenSSH
config.
CLI route syntax is explicit when needed:
# exact known aliases are profile references
sshkeeper route set prod --jumps bastion,dmz-gw
# force the interpretation
sshkeeper route set prod --jumps profile:bastion,raw:ops@external-gw:2222
An unprefixed value matching an existing sshkeeper alias becomes a profile hop; an unknown value remains a raw OpenSSH target.
Password or key-passphrase authentication on an intermediate profile hop is rejected with a clear error for now. The current PTY secret flow can safely feed the target profile, but it cannot reliably route different vault secrets to multiple OpenSSH prompts in a jump chain. Key/agent bastions are supported.
Route editor in the TUI
Ctrl+X → Route, or / while the Route field is focused, opens an ordered
route editor.
Enteradds an existing server profile as a hop.x/ Delete removes a hop.[/]moves a hop up/down in the chain./filters the available profiles.raw:<target>remains available in the editable field for arbitrary OpenSSH targets.
The server form no longer claims that a picker exists while still requiring a comma-separated string.
Server Actions vs Manage
Ctrl+X is now strictly about the selected server:
- Connect
- Connect with tunnels
- Start tunnels only / in background
- Port forwards
- Route
- Test connection
- Edit
- Delete
Press m for global management:
- Groups
- Tags
- Command templates
- Running tunnels
- Import SSH config
- Export
- Vault lock / password change
Groups now have their own manager with server counts, create/rename/delete operations, and safe delete consequences.
Context-aware server form
The form now exposes only authentication fields that matter:
| Auth | Fields |
|---|---|
| password | Password |
| key | Identity File |
| key_passphrase | Identity File + Key passphrase |
| agent | no credential fields |
/ opens context-specific pickers:
- Auth method → auth list
- Identity File → detected private keys in
~/.ssh - Route → profile/chain editor
- Group → existing groups
- Startup Command → global command templates (the command is copied, not linked)
- Tags → multi-select existing tags; new tags can still be typed manually
CLI consistency fixes
All writers now go through the same route semantics.
add,edit,route set, TUI save and SSH config import all create the same canonical route model.- SSH config import is two-pass: profiles are created first, then
ProxyJumpaliases are resolved against the complete imported/existing profile set. edituses Cobra'sChanged()state, so--group '',--notes '',--startup-command '',--identity-file '',--proxy-jump ''and--tags ''can actually clear values.- An empty server
Userno longer produces the invalid target@host; OpenSSH is allowed to choose its configured/current user. - Shared server/auth/route validation replaced duplicated partial checks.
Vault identity follows the server, not its alias
New and migrated server secrets are stored under stable server IDs. Renaming a profile no longer requires a risky copy/delete of secrets keyed by alias.
Legacy server:<alias>:<type> records remain readable and are lazily migrated
when used. vault list understands both legacy and stable-ID records and
resolves stable IDs back to current aliases when the database is available.
TUI server save now commits the database change before rewriting vault state; if the vault step fails, the profile/tags are rolled back instead of leaving the database and vault disagreeing.
Port forward fixes
Two concrete TUI bugs are fixed:
- Editing a disabled forward no longer silently re-enables it.
- Remote-forward preview now uses the same semantic builder as validation/save, so listen/target endpoints cannot be shown reversed.
The editor has an explicit Enabled toggle and the forward list supports Space
to enable/disable a rule quickly.
Tunnel fixes
Profile routes work consistently for foreground and background tunnels. Background tunnels keep their generated temporary SSH config for the lifetime of the process and remove it on stop/stop-all.
Tunnel Manager now reports running separately from tracked; stale tracked
state is no longer counted as a running process.
Cleanup
- Removed the stale per-server
CommandTemplate.ServerIDfield; command templates are global. - Removed an unused legacy
model.Secrettype; the encrypted vault has its own actual storage model. - Added regression coverage for stable route references, deletion protection, cycle detection, stable-ID vault metadata, contextual auth fields, Manage, route/identity/tag/startup pickers, and forward semantics.
Upgrade notes
On first start, v0.4.0 automatically creates groups and
server_route_hops, links existing group names, and converts existing route
text/JSON. The old columns are retained for compatibility.
As always, keep a copy of ~/.local/share/sshkeeper/ before a major upgrade if
the data matters to you.
Install
tar -xzf sshkeeper_v0.4.0_linux_amd64.tar.gz
sudo install -m 0755 sshkeeper_v0.4.0_linux_amd64/sshkeeper /usr/local/bin/sshkeeper
Verify downloads against checksums.txt. Linux and macOS are the primary
release targets. Windows remains experimental and requires OpenSSH Client
(ssh.exe) in PATH.