docs: add v0.4.0 workflow guide and release notes

This commit is contained in:
mirivlad 2026-09-05 21:00:21 +08:00
parent db25f9011b
commit 0151076ca9
3 changed files with 265 additions and 32 deletions

View File

@ -22,11 +22,11 @@ port forwarding management.
- Password and key-passphrase auth through a PTY prompt handler, without putting - Password and key-passphrase auth through a PTY prompt handler, without putting
secrets in command-line arguments. secrets in command-line arguments.
- Key, SSH-agent, password, and key+passphrase auth modes. - Key, SSH-agent, password, and key+passphrase auth modes.
- **Routes / ProxyJump**manage bastion hosts and jump chains with human-readable display. - **Routes / ProxyJump**ordered bastion chains with stable references to sshkeeper profiles; profile renames do not break routes.
- **Port forwarding** — named local/remote/SOCKS forwards with type selector, validation, and OpenSSH preview. - **Port forwarding** — named local/remote/SOCKS forwards with type selector, validation, and OpenSSH preview.
- **Tunnel management** — start/stop/list background tunnels, PID tracking, runtime state. - **Tunnel management** — start/stop/list background tunnels, PID tracking, runtime state.
- **Tunnel vs Forward** — clear separation: forward = saved rule, tunnel = running SSH process. - **Tunnel vs Forward** — clear separation: forward = saved rule, tunnel = running SSH process.
- Groups, tags, command templates, search by metadata/routes/forward ports, and OpenSSH config generation. - First-class groups, multi-select tags, command templates, search by metadata/routes/forward ports, and OpenSSH config generation.
- Import from `~/.ssh/config` and simple tab-separated export. - Import from `~/.ssh/config` and simple tab-separated export.
## Install ## Install
@ -66,11 +66,11 @@ Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
- Primary public repository: [github.com/mirivlad/sshkeeper](https://github.com/mirivlad/sshkeeper) - Primary public repository: [github.com/mirivlad/sshkeeper](https://github.com/mirivlad/sshkeeper)
- Self-hosted mirror: `git@git.mirv.top:mirivlad/sshkeeper` - Self-hosted mirror: `git@git.mirv.top:mirivlad/sshkeeper`
### Install from release (after v0.2.0 publication) ### Install from release
```bash ```bash
tar -xzf sshkeeper_v0.2.0_linux_amd64.tar.gz tar -xzf sshkeeper_v0.4.0_linux_amd64.tar.gz
sudo install -m 0755 sshkeeper_v0.2.0_linux_amd64/sshkeeper /usr/local/bin/sshkeeper sudo install -m 0755 sshkeeper_v0.4.0_linux_amd64/sshkeeper /usr/local/bin/sshkeeper
sshkeeper sshkeeper
``` ```
@ -140,7 +140,8 @@ it to DEL (in xterm, `backarrowKey: false`).
| Ctrl+E | Edit server | | Ctrl+E | Edit server |
| Ctrl+F | Search | | Ctrl+F | Search |
| Ctrl+W | Manage port forwards for selected server | | Ctrl+W | Manage port forwards for selected server |
| Ctrl+X | Action menu (connect, tunnels, forwards, route, test, edit, delete, import/export, vault actions) | | Ctrl+X | Server actions (connect, tunnels, forwards, route, test, edit, delete) |
| m | Manage groups, tags, command templates, running tunnels, import/export, and vault |
| Ins | Select / deselect a server | | Ins | Select / deselect a server |
| ? | Quick help (hotkeys) | | ? | Quick help (hotkeys) |
| Ctrl+H | Full documentation | | Ctrl+H | Full documentation |
@ -157,12 +158,25 @@ In add/edit forms:
|-----|--------| |-----|--------|
| Tab / Down | Next field | | Tab / Down | Next field |
| Shift+Tab / Up | Previous field | | Shift+Tab / Up | Previous field |
| `/` on Auth Method or Group | Pick from list | | `/` on Auth, Identity File, Route, Group, Startup Command, or Tags | Open the relevant picker/editor |
| Enter | Move to action / activate | | Enter | Move to action / activate |
| Esc | Back | | Esc | Back |
## Routes, Tunnels, and Port Forwards ## Routes, Tunnels, and Port Forwards
Routes are stored as ordered hops. If a hop matches an existing sshkeeper profile,
sshkeeper stores a stable reference to that profile ID, not the mutable alias. The
connection planner resolves the profile's real host/user/port/key and writes a
temporary OpenSSH config for the session, so a sshkeeper bastion does **not** need
a matching `Host` entry in `~/.ssh/config`.
Use `profile:<alias>` to require a profile reference and `raw:<target>` to require
a literal OpenSSH jump target. An unprefixed exact known alias is treated as a
profile; any other value remains a raw target.
In the TUI, `/` on Route opens the ordered route editor: `Enter` adds a profile,
`x`/Delete removes a hop, and `[`/`]` moves it.
### Jump host (single bastion) ### Jump host (single bastion)
```bash ```bash
@ -248,11 +262,11 @@ key-passphrase authentication so the PTY prompt handler can provide the secret.
| Action | Command | TUI | Description | | Action | Command | TUI | Description |
|--------|---------|-----|-------------| |--------|---------|-----|-------------|
| Connect | `sshkeeper connect <alias>` | `Enter` | Standard SSH session, no port forwards | | Connect | `sshkeeper connect <alias>` | `Enter` | Standard SSH session, no port forwards |
| Connect with tunnels | `sshkeeper tunnel <alias>` | Action menu → Connect with tunnels | SSH session with all enabled forwards active | | Connect with tunnels | `sshkeeper tunnel <alias>` | Server Actions → Connect with tunnels | SSH session with all enabled forwards active |
| Start tunnels only | `sshkeeper tunnel <alias> --forward-only` | Action menu → Start tunnels only | Foreground tunnel, no shell | | Start tunnels only | `sshkeeper tunnel <alias> --forward-only` | Server Actions → Start tunnels only | Foreground tunnel, no shell |
| Start tunnels in background | `sshkeeper tunnel <alias> --background` | Action menu → Start tunnels in background | Detached tunnel process with PID tracking | | Start tunnels in background | `sshkeeper tunnel <alias> --background` | Server Actions → Start tunnels in background | Detached tunnel process with PID tracking |
| Manage port forwards | `sshkeeper forward` | Action menu → Manage port forwards | Add/edit/delete forward rules | | Port forwards | `sshkeeper forward` | Server Actions → Port forwards (or `Ctrl+W`) | Add/edit/enable/delete forward rules |
| Manage tunnels | `sshkeeper tunnel list/stop/stop-all` | Action menu → Manage tunnels | View running tunnels and stop them | | Running tunnels | `sshkeeper tunnel list/stop/stop-all` | `m` → Running tunnels | View tracked/running tunnels and stop them |
## Vault ## Vault

View File

@ -166,7 +166,8 @@ Auth: agent Group: - Status: ?
| `Enter` | Подключиться к серверу | | `Enter` | Подключиться к серверу |
| `Ctrl+A` | Добавить сервер | | `Ctrl+A` | Добавить сервер |
| `Ctrl+E` | Редактировать сервер | | `Ctrl+E` | Редактировать сервер |
| `Ctrl+X` | Меню действий | | `Ctrl+X` | Действия выбранного сервера |
| `m` | Глобальное меню Manage |
| `Ctrl+F` | Поиск | | `Ctrl+F` | Поиск |
| `Ins` | Выбрать/снять выбор | | `Ins` | Выбрать/снять выбор |
| `?` | Краткая справка по клавишам | | `?` | Краткая справка по клавишам |
@ -201,7 +202,8 @@ sshkeeper — Quick Help
Ctrl+A Add server Ctrl+A Add server
Ctrl+E Edit server Ctrl+E Edit server
Ctrl+F Search Ctrl+F Search
Ctrl+X Action menu Ctrl+X Server actions
m Manage
Ins Select / deselect Ins Select / deselect
Port forwards Port forwards
@ -252,7 +254,8 @@ sshkeeper — Full Help
Ctrl+A Add server Ctrl+A Add server
Ctrl+E Edit server Ctrl+E Edit server
Ctrl+F Search Ctrl+F Search
Ctrl+X Action menu Ctrl+X Server actions
m Manage
Ins Select / deselect Ins Select / deselect
... ...
@ -287,7 +290,7 @@ Add Server
User: root User: root
Auth Method: key Auth Method: key
Identity File: ~/.ssh/id_ed25519 Identity File: ~/.ssh/id_ed25519
Route hops: bastion Route: profile:bastion
Group: KP Group: KP
Notes: Main mail server Notes: Main mail server
Startup Command: tmux attach -t ops Startup Command: tmux attach -t ops
@ -311,8 +314,12 @@ Add Server
|---------|----------| |---------|----------|
| `Tab` или `↓` | Следующее поле | | `Tab` или `↓` | Следующее поле |
| `Shift+Tab` или `↑` | Предыдущее поле | | `Shift+Tab` или `↑` | Предыдущее поле |
| `/` на Auth Method | Выбрать из списка (password/key/key_passphrase/agent) | | `/` на Auth Method | Выбрать password/key/key_passphrase/agent |
| `/` на Group | Выбрать из существующих групп | | `/` на Identity File | Выбрать приватный ключ из `~/.ssh` |
| `/` на Route | Открыть редактор цепочки бастионов |
| `/` на Group | Выбрать существующую группу |
| `/` на Startup Command | Скопировать команду из глобального шаблона |
| `/` на Tags | Multi-select существующих тегов; новые можно ввести вручную |
| `Enter` на Test | Проверить подключение | | `Enter` на Test | Проверить подключение |
| `Enter` на Save | Сохранить | | `Enter` на Save | Сохранить |
| `Esc` | Назад; при изменённых данных сначала запросить подтверждение сброса | | `Esc` | Назад; при изменённых данных сначала запросить подтверждение сброса |
@ -378,9 +385,16 @@ ROUTE: ⇒ bastion → dmz-gw → … → root@secure.internal:22
### Настройка маршрута ### Настройка маршрута
**Через TUI:** **Через TUI:**
1. Добавьте/редактируйте сервер или выберите `Ctrl+X` → "Manage route" 1. Добавьте/редактируйте сервер или выберите `Ctrl+X`**Route**.
2. В поле "Route hops" введите бастионы через запятую: `bastion,dmz-gw` 2. Нажмите `/` на поле Route — откроется список существующих профилей.
3. Или введите адрес напрямую: `user@bastion.example.com:2222` 3. `Enter` добавляет выбранный профиль в ordered chain, `x`/Delete удаляет hop, `[`/`]` меняет порядок.
4. Для произвольной OpenSSH-цели оставьте escape hatch в поле: `raw:user@bastion.example.com:2222`.
Ссылка на профиль хранится по стабильному ID. Переименование alias бастиона не
ломает зависимые маршруты, а удалить используемый бастион sshkeeper не даст,
пока он присутствует в route других серверов. Во время подключения profile-hop
разрешается из БД sshkeeper; отдельная запись с тем же alias в `~/.ssh/config`
не требуется.
**Через CLI:** **Через CLI:**
@ -413,7 +427,7 @@ sshkeeper route clear web
### Управление forwards через TUI ### Управление forwards через TUI
1. Выберите сервер на главном экране 1. Выберите сервер на главном экране
2. Нажмите `Ctrl+X`"Manage port forwards" 2. Нажмите `Ctrl+X`**Port forwards**
3. Откроется список forwards: 3. Откроется список forwards:
``` ```
@ -426,7 +440,7 @@ Selected
Port 127.0.0.1:15432 on this machine will be forwarded through web to 127.0.0.1:5432. Port 127.0.0.1:15432 on this machine will be forwarded through web to 127.0.0.1:5432.
ssh -L 127.0.0.1:15432:127.0.0.1:5432 ssh -L 127.0.0.1:15432:127.0.0.1:5432
Ctrl+A: add | Ctrl+E/Enter: edit | Ctrl+D: delete | Esc: back Ctrl+A: add | Ctrl+E/Enter: edit | Space: enable/disable | Ctrl+D: delete | Esc: back
``` ```
Строки и пояснение выбранного forward сокращаются по экранным ячейкам, а не Строки и пояснение выбранного forward сокращаются по экранным ячейкам, а не
@ -438,6 +452,7 @@ Selected
|---------|----------| |---------|----------|
| `Ctrl+A` | Добавить forward | | `Ctrl+A` | Добавить forward |
| `Enter` или `Ctrl+E` | Редактировать выбранный | | `Enter` или `Ctrl+E` | Редактировать выбранный |
| `Space` | Включить/выключить правило |
| `Ctrl+D` | Удалить (с подтверждением) | | `Ctrl+D` | Удалить (с подтверждением) |
| `Esc` | Назад | | `Esc` | Назад |
@ -514,7 +529,7 @@ forward и сейчас поддерживает только `key` или `agen
### Управление туннелями ### Управление туннелями
**Через TUI:** **Через TUI:**
1. Нажмите `Ctrl+X` → "Manage tunnels" 1. Нажмите `m` → **Running tunnels**
2. Список запущенных туннелей: 2. Список запущенных туннелей:
``` ```
@ -730,7 +745,7 @@ sshkeeper connect secure
```bash ```bash
# В TUI: выбрать несколько серверов (Ins), затем: # В TUI: выбрать несколько серверов (Ins), затем:
# Ctrl+X → Run template → выбрать шаблон # Ctrl+R → выбрать шаблон
# Команда выполнится на всех выбранных серверах # Команда выполнится на всех выбранных серверах
``` ```
@ -746,13 +761,14 @@ sshkeeper connect secure
| `Ctrl+A` | Добавить сервер | | `Ctrl+A` | Добавить сервер |
| `Ctrl+E` | Редактировать сервер | | `Ctrl+E` | Редактировать сервер |
| `Ctrl+F` | Поиск | | `Ctrl+F` | Поиск |
| `Ctrl+X` | Меню действий | | `Ctrl+X` | Действия выбранного сервера |
| `m` | Глобальное меню Manage |
| `Ins` | Выбрать/снять выбор | | `Ins` | Выбрать/снять выбор |
| `?` | Краткая справка по клавишам | | `?` | Краткая справка по клавишам |
| `Ctrl+H` | Полная справка по приложению | | `Ctrl+H` | Полная справка по приложению |
| `Ctrl+Q` | Выход | | `Ctrl+Q` | Выход |
### Меню действий (Ctrl+X) ### Действия сервера (Ctrl+X)
| Действие | Описание | | Действие | Описание |
|----------|----------| |----------|----------|
@ -760,13 +776,21 @@ sshkeeper connect secure
| Connect with tunnels | SSH + все активные forwards | | Connect with tunnels | SSH + все активные forwards |
| Start tunnels only | Туннель без shell | | Start tunnels only | Туннель без shell |
| Start tunnels in background | Фоновый туннель | | Start tunnels in background | Фоновый туннель |
| Manage port forwards | Управление forwards | | Port forwards | Управление forwards выбранного сервера |
| Manage tunnels | Список туннелей | | Route | Ordered chain бастионов выбранного сервера |
| Manage route | Настройка маршрута |
| Test connection | Проверка подключения | | Test connection | Проверка подключения |
| Edit | Редактирование сервера | | Edit | Редактирование сервера |
| Delete | Удаление (с подтверждением) | | Delete | Удаление (с подтверждением) |
| Import | Импорт из `~/.ssh/config` и обновление списка |
### Manage (`m`)
| Действие | Описание |
|----------|----------|
| Groups | Создание, переименование и удаление групп с количеством серверов |
| Tags | Управление тегами |
| Command templates | Глобальные шаблоны команд |
| Running tunnels | Список и остановка фоновых туннелей |
| Import SSH config | Импорт из `~/.ssh/config` |
| Export | Выход в терминал и печать экспорта | | Export | Выход в терминал и печать экспорта |
| Vault: lock | Заблокировать vault в текущем процессе | | Vault: lock | Заблокировать vault в текущем процессе |
| Vault: change password | Выход в терминал и смена master password | | Vault: change password | Выход в терминал и смена master password |
@ -777,7 +801,7 @@ sshkeeper connect secure
|---------|----------| |---------|----------|
| `Tab` / `↓` | Следующее поле | | `Tab` / `↓` | Следующее поле |
| `Shift+Tab` / `↑` | Предыдущее поле | | `Shift+Tab` / `↑` | Предыдущее поле |
| `/` | Выбрать из списка (Auth Method, Group) | | `/` | Открыть picker/editor для Auth, key, Route, Group, Startup Command или Tags |
| `Enter` | Действие / переход | | `Enter` | Действие / переход |
| `Esc` | Назад / отмена | | `Esc` | Назад / отмена |

195
docs/releases/v0.4.0.md Normal file
View File

@ -0,0 +1,195 @@
# 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
```bash
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`.