611b794fc7
- pkg/crypto: AES-256-GCM encryption with PBKDF2 key derivation - 100k iterations, 16-byte salt, SHA-256 - Encrypt/Decrypt/IsEncrypted/HashPassword - Storage layer encryption: - JSONStorage.SetPassword() enables transparent encrypt/decrypt - readJSON auto-decrypts, replace* auto-encrypts - pkg/knownhosts: TOFU host key verification - Verify/Add/Remove host keys - HostKeyCallback for SSH config - SSH client security: - SetHostKeyCallback() replaces InsecureIgnoreHostKey() - SetPassphraseCallback() for encrypted private keys - getKeySigner() tries passphrase on encrypted keys - Models: AppConfig gains EncryptionEnabled, PasswordHash, KnownHostsFile
151 lines
7.3 KiB
Markdown
151 lines
7.3 KiB
Markdown
# Changelog
|
||
|
||
## [Unreleased] — 2025-01-31
|
||
|
||
### Fixed
|
||
- TUI host list footer now includes `Ctrl+E:edit`
|
||
- New hosts/keys/snippets created via TUI form now get UUID + timestamps (`CreatedAt`, `UpdatedAt`)
|
||
- Storage layer auto-generates UUID + timestamps on save if empty (defense-in-depth)
|
||
- **host_form_tab.go**: Space/Left/Right keys now passthrough to text input when not on auth type field (fixes typing spaces and cursor navigation)
|
||
- **tui.go**: Key and snippet list tabs now properly refresh after save/delete (mirrors host flow)
|
||
- **tabs.go**: WindowSizeMsg now forwarded to ALL tabs, not just the active one (fixes stale dimensions after resize)
|
||
|
||
### Changed
|
||
- All tab footers are now context-specific (no global status bar)
|
||
- SFTP browser uses full-width panes (`t.width/2`)
|
||
- **sftp_browser_tab.go footer**: Added `Enter/→:open`, `←/Backspace:up`
|
||
- **form tabs footer**: Added `Shift+Tab:prev`, `↑↓:nav`
|
||
|
||
### Responsive Layout (Done)
|
||
- **NEW `responsive.go`**: Shared helpers (`wrapFooter`, `adaptiveSidePad`, `clampWidth`, `truncateStr`)
|
||
- Footer auto-wraps to multi-line when terminal is narrow (full labels preserved)
|
||
- Box width clamped to terminal width — no more overflow
|
||
- Host/key/snippet rows truncated with `…` when names are too long; compact row format on narrow screens
|
||
- SFTP panes stack vertically when terminal < 50 cols
|
||
- Tab bar truncates tab names on overflow
|
||
- Form contentW minimum lowered from 50 to 30 for mobile (Termux)
|
||
- Fixed: key_list_tab & snippet_list_tab were dropping last data row (off-by-one in `rows[:len(rows)-1]`)
|
||
- Fixed: SFTP pane width — lipgloss `Width(n)` renders `n+2` chars (border extra). halfW now `t.width/2-2` to compensate. File names truncate to `…` within inner text area. Filter line also truncated.
|
||
- Verified: zero overflow at 80, 60, 50 (side-by-side), 40, 30 cols (stacked)
|
||
|
||
### SFTP Stacked Mode Fix (Done)
|
||
- Stacked mode (<50 cols) now renders only the active pane (Local or Remote)
|
||
- Added pane indicator bar: `[Local] Remote` with active pane highlighted
|
||
- Side-by-side mode (≥50 cols) unchanged — both panes remain visible
|
||
- Tab key now visually switches between panes on narrow terminals
|
||
- Default active pane: Local (was Remote)
|
||
- Border height fix: pane uses fixed `Height(maxH)` — border no longer shrinks/grows when scrolling
|
||
- `renderPane` now takes `maxH` parameter to control content area height
|
||
- Stacked: `maxH = t.height - 6`; Side-by-side: `maxH = t.height - 5`
|
||
- Breakpoints: compact (<60), medium (60–100), wide (≥100)
|
||
|
||
### SFTP Performance Fix
|
||
- Added directory listing cache (`dirCache map[string][]os.FileInfo`) to each pane
|
||
- Cache hit: instant navigation (no disk/network call)
|
||
- Cache miss: read from disk/SFTP, store in cache
|
||
- `R` key now clears cache for current directory and forces re-read
|
||
- Cache is per-path: navigating to a previously visited folder is instant
|
||
|
||
### SFTP File Transfer Progress Bar
|
||
- Added progress bar during upload/download: `[████████░░░░] 67% 12.3MB/18.5MB`
|
||
- `progressWriter` wraps `io.Writer` and reports bytes transferred
|
||
- Footer shows live progress during transfer
|
||
- Auto-refresh destination pane after transfer completes
|
||
- Success message shown for 1 second before clearing
|
||
|
||
### SFTP Auto-Refresh Fix
|
||
- Added `program *tea.Program` field to SFTPBrowserTab and Model
|
||
- `SetProgram()` method allows goroutines to send messages to Bubble Tea
|
||
- After transfer: `t.program.Send(sftpRefreshMsg{})` triggers re-render
|
||
- Model stores program reference, passes it to SFTP tab on creation
|
||
|
||
### SFTP Bug Fixes
|
||
- Auto-refresh: clear dirCache before refresh after transfer/delete/mkdir
|
||
- Cache was stale after file operations — old entries returned instead of fresh
|
||
- Filter mode: allow command keys (c, d, n, r) and navigation (↑↓, Tab) to pass through
|
||
- Filter mode no longer blocks transfer, delete, mkdir, or pane switch
|
||
- Delete: clear cache + trigger re-render after file removal
|
||
- Mkdir: clear cache + trigger re-render after directory creation
|
||
|
||
### SFTP Rename Feature
|
||
- Added `M` key to rename files and directories
|
||
- Rename mode: shows input with current name, type new name, Enter to confirm
|
||
- Works on both Local and Remote panes
|
||
- Clears cache and refreshes after rename
|
||
- Footer updated: `M:rename` added
|
||
- Filter mode allows `M` to pass through
|
||
|
||
## v1.0.0 (2025-01-30)
|
||
|
||
### Added
|
||
|
||
- Initial release of Hostkeeper SSH/SFTP management tool
|
||
- **Host management**: CRUD operations for SSH hosts (add, list, edit, delete)
|
||
- **SSH connections**: Native SSH (default) and Go SSH direct mode
|
||
- **TUI interface**: Interactive host browser using Bubble Tea
|
||
- **Export/Import**: JSON export and import with merge/replace strategies
|
||
- **Cross-platform builds**: Support for Linux, macOS, Windows, Termux
|
||
- **Shell completion**: Bash, Zsh, Fish, and PowerShell support
|
||
- **Configuration**: File-based credential storage with `0600` permissions
|
||
- **Tag and group**: Host categorization with tags and groups
|
||
- **Search and filter**: Filter hosts by group, tag, or text search
|
||
|
||
### Commands
|
||
|
||
- `hostkeeper add` — Add SSH hosts (interactive and flag-based)
|
||
- `hostkeeper list` — List hosts with table/JSON output
|
||
- `hostkeeper connect` — Connect to hosts with custom timeout
|
||
- `hostkeeper edit` — Edit host configurations
|
||
- `hostkeeper delete` — Delete hosts with confirmation
|
||
- `hostkeeper export` — Export data to JSON
|
||
- `hostkeeper import` — Import data with merge/replace
|
||
- `hostkeeper tui` — Terminal user interface
|
||
- `hostkeeper completion` — Shell completion generation
|
||
- `hostkeeper version` — Version information
|
||
|
||
### Technical
|
||
|
||
- Cobra CLI framework for command structure
|
||
- Bubble Tea TUI with keyboard navigation
|
||
- Go SSH client for direct connections
|
||
- Comprehensive test suite with unit and integration tests
|
||
- Build automation via Makefile and build.sh
|
||
|
||
### Notes
|
||
|
||
- Initial MVP release, all core features functional
|
||
- Encrypted storage planned for future release
|
||
- Interactive shell in Go SSH direct mode not yet available
|
||
|
||
## Phase 2 — Security Enhancement
|
||
|
||
### AES-256-GCM Encryption
|
||
- New `pkg/crypto/crypto.go`: AES-256-GCM encrypt/decrypt with PBKDF2 key derivation
|
||
- 100,000 iterations, 16-byte salt, SHA-256 key derivation
|
||
- `Encrypt(plaintext, password)` → base64(salt + nonce + ciphertext)
|
||
- `Decrypt(encoded, password)` → plaintext
|
||
- `IsEncrypted(data)` checks if data looks like encrypted content
|
||
|
||
### Storage Layer Encryption
|
||
- `JSONStorage` now has `password` field for master encryption key
|
||
- `SetPassword()`, `GetPassword()`, `IsEncrypted()` methods
|
||
- `readJSON()` auto-decrypts if password is set and data is encrypted
|
||
- `replaceHosts/KeyPairs/Snippets()` auto-encrypt before writing
|
||
- All existing CRUD operations transparently encrypt/decrypt
|
||
|
||
### Known Hosts Verification
|
||
- New `pkg/knownhosts/knownhosts.go`: TOFU (Trust-On-First-Use) model
|
||
- `KnownHosts` manages `known_hosts` file (JSON format)
|
||
- `Verify()` checks if host key matches stored key
|
||
- `HostKeyCallback()` returns `cryptossh.HostKeyCallback` for SSH config
|
||
- Warns on key mismatch (potential MITM attack)
|
||
|
||
### SSH Client Security
|
||
- `Client` now supports `hostKeyCallback` and `passphraseCallback`
|
||
- `SetHostKeyCallback()` — replaces `InsecureIgnoreHostKey()`
|
||
- `SetPassphraseCallback()` — prompts for passphrase on encrypted keys
|
||
- `getKeySigner()` tries passphrase callback if key is encrypted
|
||
|
||
### Models Updated
|
||
- `AppConfig`: added `EncryptionEnabled`, `PasswordHash`, `KnownHostsFile`
|