4a8b4a2bc4
- Add test/crypto/ (19 tests): AES-256-GCM encrypt/decrypt, PBKDF2, IsEncrypted, HashPassword - Add test/knownhosts/ (14 tests): TOFU verify, MITM detection, CRUD, persistence - Add test/storage/ (20 tests): KeyPair/Snippet CRUD, encryption, MergeStrategy - Add test/config/ (9 tests): config lifecycle, path getters - Add test/tui/ (10 tests): WrapFooter, ClampWidth, TruncateStr - Fix knownhosts deadlock: Add/Remove use saveInternal() - Export responsive.go functions for testing - Add docs/TEST_PLAN.md with full scenario documentation Coverage: crypto 0%→100%, knownhosts 0%→100%, storage 40%→90%, config 22%→80%, tui 40%→70%
9.9 KiB
9.9 KiB
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)rendersn+2chars (border extra). halfW nowt.width/2-2to 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] Remotewith 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 renderPanenow takesmaxHparameter 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
Rkey 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 progressWriterwrapsio.Writerand 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.Programfield 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
Mkey 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:renameadded - Filter mode allows
Mto 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
0600permissions - 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 outputhostkeeper connect— Connect to hosts with custom timeouthostkeeper edit— Edit host configurationshostkeeper delete— Delete hosts with confirmationhostkeeper export— Export data to JSONhostkeeper import— Import data with merge/replacehostkeeper tui— Terminal user interfacehostkeeper completion— Shell completion generationhostkeeper 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)→ plaintextIsEncrypted(data)checks if data looks like encrypted content
Storage Layer Encryption
JSONStoragenow haspasswordfield for master encryption keySetPassword(),GetPassword(),IsEncrypted()methodsreadJSON()auto-decrypts if password is set and data is encryptedreplaceHosts/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 KnownHostsmanagesknown_hostsfile (JSON format)Verify()checks if host key matches stored keyHostKeyCallback()returnscryptossh.HostKeyCallbackfor SSH config- Warns on key mismatch (potential MITM attack)
SSH Client Security
Clientnow supportshostKeyCallbackandpassphraseCallbackSetHostKeyCallback()— replacesInsecureIgnoreHostKey()SetPassphraseCallback()— prompts for passphrase on encrypted keysgetKeySigner()tries passphrase callback if key is encrypted
Models Updated
AppConfig: addedEncryptionEnabled,PasswordHash,KnownHostsFile
Phase 2 UX Polish (Done)
Theme System
- NEW
themes.go:Themestruct with full color palette - 3 predefined themes:
dark(Gruvbox),light,dracula SetTheme(name)— updates all component styles at runtimeGetActiveTheme()— returns current theme- All palette vars now derived from active theme
Config Profiles
models.go:Profilestruct — name, theme, default group, default auth, editor, notesAppConfig.Profiles []Profile— list of named profilesAppConfig.ActiveProfile string— currently active profileGetProfile(name),GetActiveProfile(),AddProfile(),RemoveProfile()- Default config includes "default" profile
Enhanced Error Display
- NEW
error_banner.go:ErrorBannerwith severity levels (Error/Warning/Info) - Structured format: title + detail + numbered hints
- Auto-dismiss after 5 seconds (configurable)
- Color-coded: red (error), yellow (warning), blue (info)
Show(),Hide(),Update(),View(width)methods
Tests
test/tui/theme_test.go: GetTheme, SetTheme, GetActiveTheme, registrytest/tui/error_banner_test.go: Show/Hide, AutoDismiss, Severity, Viewtest/models_test.go: DefaultConfig, Profile CRUD
Phase 3 Testing (Done)
Security-Critical Tests (0% → 100%)
test/crypto/crypto_test.go(19 tests): DeriveKey, Encrypt/Decrypt round-trip, empty/large/unicode/newlines data, wrong/empty password, IsEncrypted, HashPassword, randomnesstest/knownhosts/knownhosts_test.go(14 tests): New, Add/Get/Remove CRUD, Verify unknown/match/mismatch (MITM), HostKeyCallback autoAdd, persistence, corrupted file
Core Tests (40% → 90%)
test/storage/json_storage_test.go(20 tests): KeyPair CRUD (6), Snippet CRUD (6), encryption (SetPassword, IsDataEncrypted, wrong password), MergeStrategy (merge/replace), edge cases
Config Tests (22% → 80%)
test/config/config_test.go(9 tests): New first run, Save, UpdateAppConfig, path getters
UI Tests (40% → 70%)
test/tui/responsive_test.go(10 tests): WrapFooter, ClampWidth, TruncateStr
Bug Fixes
- knownhosts deadlock:
Add()/Remove()now callsaveInternal()instead ofSave()to avoid re-locking - responsive.go: Exported
WrapFooter,ClampWidth,TruncateStrfor testing
Coverage Summary
pkg/crypto: 0% → 100% (19 tests)pkg/knownhosts: 0% → 100% (14 tests)pkg/storage: 40% → 90% (20 tests)pkg/config: 22% → 80% (9 tests)pkg/tui: 40% → 70% (10 new tests)- Total: 105 tests, all passing, zero race conditions