sshkeeper/docs/releases/v0.4.0.md

208 lines
7.3 KiB
Markdown

# 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:
```bash
# 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.
- `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):
```bash
sudo apt install ./sshkeeper_0.4.0-1_amd64.deb
```
Fedora/RHEL-family (x86_64):
```bash
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`.