sshkeeper/docs/releases/v0.4.0.md

7.3 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_jump and legacy route_hops remain compatibility projections for old databases/tools, not competing sources of truth.
  • Existing group_name strings are likewise migrated to first-class groups with 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+XRoute, or / while the Route field is focused, opens an ordered route editor.

  • Enter adds 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 ProxyJump aliases are resolved against the complete imported/existing profile set.
  • edit uses Cobra's Changed() state, so --group '', --notes '', --startup-command '', --identity-file '', --proxy-jump '' and --tags '' can actually clear values.
  • An empty server User no 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.ServerID field; command templates are global.
  • Removed an unused legacy model.Secret type; 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

Debian/Ubuntu (amd64):

sudo apt install ./sshkeeper_0.4.0-1_amd64.deb

Fedora/RHEL-family (x86_64):

sudo dnf install ./sshkeeper-0.4.0-1.x86_64.rpm

ARM64 packages (arm64.deb / aarch64.rpm) and the original tar.gz archives are published alongside them. Package dependencies pull in the distro OpenSSH client; user config, database and vault files are not owned or modified by the package.

Verify downloads against checksums.txt. Linux and macOS are the primary release targets. Windows remains experimental and requires OpenSSH Client (ssh.exe) in PATH.