From 0151076ca9cff4f47683372d0c02bf042188aade Mon Sep 17 00:00:00 2001 From: mirivlad Date: Sat, 5 Sep 2026 21:00:21 +0800 Subject: [PATCH] docs: add v0.4.0 workflow guide and release notes --- README.md | 38 +++++--- docs/guide.md | 64 ++++++++----- docs/releases/v0.4.0.md | 195 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 265 insertions(+), 32 deletions(-) create mode 100644 docs/releases/v0.4.0.md diff --git a/README.md b/README.md index ac002d0..90515a7 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,11 @@ port forwarding management. - Password and key-passphrase auth through a PTY prompt handler, without putting secrets in command-line arguments. - 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. - **Tunnel management** — start/stop/list background tunnels, PID tracking, runtime state. - **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. ## 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) - Self-hosted mirror: `git@git.mirv.top:mirivlad/sshkeeper` -### Install from release (after v0.2.0 publication) +### Install from release ```bash -tar -xzf sshkeeper_v0.2.0_linux_amd64.tar.gz -sudo install -m 0755 sshkeeper_v0.2.0_linux_amd64/sshkeeper /usr/local/bin/sshkeeper +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 sshkeeper ``` @@ -140,7 +140,8 @@ it to DEL (in xterm, `backarrowKey: false`). | Ctrl+E | Edit server | | Ctrl+F | Search | | 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 | | ? | Quick help (hotkeys) | | Ctrl+H | Full documentation | @@ -157,12 +158,25 @@ In add/edit forms: |-----|--------| | Tab / Down | Next 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 | | Esc | Back | ## 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:` to require a profile reference and `raw:` 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) ```bash @@ -248,11 +262,11 @@ key-passphrase authentication so the PTY prompt handler can provide the secret. | Action | Command | TUI | Description | |--------|---------|-----|-------------| | Connect | `sshkeeper connect ` | `Enter` | Standard SSH session, no port forwards | -| Connect with tunnels | `sshkeeper tunnel ` | Action menu → Connect with tunnels | SSH session with all enabled forwards active | -| Start tunnels only | `sshkeeper tunnel --forward-only` | Action menu → Start tunnels only | Foreground tunnel, no shell | -| Start tunnels in background | `sshkeeper tunnel --background` | Action menu → 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 | -| Manage tunnels | `sshkeeper tunnel list/stop/stop-all` | Action menu → Manage tunnels | View running tunnels and stop them | +| Connect with tunnels | `sshkeeper tunnel ` | Server Actions → Connect with tunnels | SSH session with all enabled forwards active | +| Start tunnels only | `sshkeeper tunnel --forward-only` | Server Actions → Start tunnels only | Foreground tunnel, no shell | +| Start tunnels in background | `sshkeeper tunnel --background` | Server Actions → Start tunnels in background | Detached tunnel process with PID tracking | +| Port forwards | `sshkeeper forward` | Server Actions → Port forwards (or `Ctrl+W`) | Add/edit/enable/delete forward rules | +| Running tunnels | `sshkeeper tunnel list/stop/stop-all` | `m` → Running tunnels | View tracked/running tunnels and stop them | ## Vault diff --git a/docs/guide.md b/docs/guide.md index 2baaff0..6e85f01 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -166,7 +166,8 @@ Auth: agent Group: - Status: ? | `Enter` | Подключиться к серверу | | `Ctrl+A` | Добавить сервер | | `Ctrl+E` | Редактировать сервер | -| `Ctrl+X` | Меню действий | +| `Ctrl+X` | Действия выбранного сервера | +| `m` | Глобальное меню Manage | | `Ctrl+F` | Поиск | | `Ins` | Выбрать/снять выбор | | `?` | Краткая справка по клавишам | @@ -201,7 +202,8 @@ sshkeeper — Quick Help Ctrl+A Add server Ctrl+E Edit server Ctrl+F Search - Ctrl+X Action menu + Ctrl+X Server actions + m Manage Ins Select / deselect Port forwards @@ -252,7 +254,8 @@ sshkeeper — Full Help Ctrl+A Add server Ctrl+E Edit server Ctrl+F Search - Ctrl+X Action menu + Ctrl+X Server actions + m Manage Ins Select / deselect ... @@ -287,7 +290,7 @@ Add Server User: root Auth Method: key Identity File: ~/.ssh/id_ed25519 - Route hops: bastion + Route: profile:bastion Group: KP Notes: Main mail server Startup Command: tmux attach -t ops @@ -311,8 +314,12 @@ Add Server |---------|----------| | `Tab` или `↓` | Следующее поле | | `Shift+Tab` или `↑` | Предыдущее поле | -| `/` на Auth Method | Выбрать из списка (password/key/key_passphrase/agent) | -| `/` на Group | Выбрать из существующих групп | +| `/` на Auth Method | Выбрать password/key/key_passphrase/agent | +| `/` на Identity File | Выбрать приватный ключ из `~/.ssh` | +| `/` на Route | Открыть редактор цепочки бастионов | +| `/` на Group | Выбрать существующую группу | +| `/` на Startup Command | Скопировать команду из глобального шаблона | +| `/` на Tags | Multi-select существующих тегов; новые можно ввести вручную | | `Enter` на Test | Проверить подключение | | `Enter` на Save | Сохранить | | `Esc` | Назад; при изменённых данных сначала запросить подтверждение сброса | @@ -378,9 +385,16 @@ ROUTE: ⇒ bastion → dmz-gw → … → root@secure.internal:22 ### Настройка маршрута **Через TUI:** -1. Добавьте/редактируйте сервер или выберите `Ctrl+X` → "Manage route" -2. В поле "Route hops" введите бастионы через запятую: `bastion,dmz-gw` -3. Или введите адрес напрямую: `user@bastion.example.com:2222` +1. Добавьте/редактируйте сервер или выберите `Ctrl+X` → **Route**. +2. Нажмите `/` на поле Route — откроется список существующих профилей. +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:** @@ -413,7 +427,7 @@ sshkeeper route clear web ### Управление forwards через TUI 1. Выберите сервер на главном экране -2. Нажмите `Ctrl+X` → "Manage port forwards" +2. Нажмите `Ctrl+X` → **Port 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. 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 сокращаются по экранным ячейкам, а не @@ -438,6 +452,7 @@ Selected |---------|----------| | `Ctrl+A` | Добавить forward | | `Enter` или `Ctrl+E` | Редактировать выбранный | +| `Space` | Включить/выключить правило | | `Ctrl+D` | Удалить (с подтверждением) | | `Esc` | Назад | @@ -514,7 +529,7 @@ forward и сейчас поддерживает только `key` или `agen ### Управление туннелями **Через TUI:** -1. Нажмите `Ctrl+X` → "Manage tunnels" +1. Нажмите `m` → **Running tunnels** 2. Список запущенных туннелей: ``` @@ -730,7 +745,7 @@ sshkeeper connect secure ```bash # В TUI: выбрать несколько серверов (Ins), затем: -# Ctrl+X → Run template → выбрать шаблон +# Ctrl+R → выбрать шаблон # Команда выполнится на всех выбранных серверах ``` @@ -746,13 +761,14 @@ sshkeeper connect secure | `Ctrl+A` | Добавить сервер | | `Ctrl+E` | Редактировать сервер | | `Ctrl+F` | Поиск | -| `Ctrl+X` | Меню действий | +| `Ctrl+X` | Действия выбранного сервера | +| `m` | Глобальное меню Manage | | `Ins` | Выбрать/снять выбор | | `?` | Краткая справка по клавишам | | `Ctrl+H` | Полная справка по приложению | | `Ctrl+Q` | Выход | -### Меню действий (Ctrl+X) +### Действия сервера (Ctrl+X) | Действие | Описание | |----------|----------| @@ -760,13 +776,21 @@ sshkeeper connect secure | Connect with tunnels | SSH + все активные forwards | | Start tunnels only | Туннель без shell | | Start tunnels in background | Фоновый туннель | -| Manage port forwards | Управление forwards | -| Manage tunnels | Список туннелей | -| Manage route | Настройка маршрута | +| Port forwards | Управление forwards выбранного сервера | +| Route | Ordered chain бастионов выбранного сервера | | Test connection | Проверка подключения | | Edit | Редактирование сервера | | Delete | Удаление (с подтверждением) | -| Import | Импорт из `~/.ssh/config` и обновление списка | + +### Manage (`m`) + +| Действие | Описание | +|----------|----------| +| Groups | Создание, переименование и удаление групп с количеством серверов | +| Tags | Управление тегами | +| Command templates | Глобальные шаблоны команд | +| Running tunnels | Список и остановка фоновых туннелей | +| Import SSH config | Импорт из `~/.ssh/config` | | Export | Выход в терминал и печать экспорта | | Vault: lock | Заблокировать vault в текущем процессе | | Vault: change password | Выход в терминал и смена master password | @@ -777,7 +801,7 @@ sshkeeper connect secure |---------|----------| | `Tab` / `↓` | Следующее поле | | `Shift+Tab` / `↑` | Предыдущее поле | -| `/` | Выбрать из списка (Auth Method, Group) | +| `/` | Открыть picker/editor для Auth, key, Route, Group, Startup Command или Tags | | `Enter` | Действие / переход | | `Esc` | Назад / отмена | diff --git a/docs/releases/v0.4.0.md b/docs/releases/v0.4.0.md new file mode 100644 index 0000000..411c45a --- /dev/null +++ b/docs/releases/v0.4.0.md @@ -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:` 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::` 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`.