refactor: move V1 code into v1/ subdirectory

- git mv cmd/ internal/ pkg/ test/ go.mod go.sum Makefile build.sh docs/ v1/
- Create v1/README.md with V1 documentation
- Update root README for V1 + V2 structure
- V1 still builds (cd v1 && go build ./cmd/hostkeeper) and 105 tests pass
- Root is now clean for V2 development
This commit is contained in:
swanadiva
2026-07-07 11:56:27 +07:00
parent 8ebdebedc8
commit 847989df75
68 changed files with 58 additions and 116 deletions
-128
View File
@@ -1,128 +0,0 @@
# Architecture
## Overview
Hostkeeper follows a layered architecture:
```
CLI Layer (cmd/hostkeeper/)
|
| calls
v
Application Layer (pkg/)
|
| calls
v
Storage Layer (pkg/config/, pkg/storage/)
|
| reads/writes
v
File System (~/.config/hostkeeper/)
```
## Directory Structure
```
cmd/hostkeeper/ — CLI commands (Cobra)
├── main.go — Entry point
├── root.go — Root command setup
├── add.go — Add host command
├── list.go — List hosts command
├── connect.go — Connect command
├── edit.go — Edit host command
├── delete.go — Delete host command
├── export.go — Export command
├── import.go — Import command
├── tui.go — TUI command entry
├── version.go — Version command
├── completion.go — Shell completion
└── *_test.go — CLI command tests
pkg/ — Library code
├── config/ — Configuration and data directory
├── models/ — Data models (Host, Key, Snippet)
├── storage/ — File and export/import operations
├── errors/ — Error types
└── tui/ — TUI implementation (Bubble Tea)
test/ — Test suites
├── integration/ — End-to-end integration tests
└── storage/ — Storage tests
docs/ — Documentation
├── plans/ — Design and implementation plans
├── INSTALLATION.md
├── USAGE.md
└── ARCHITECTURE.md
```
## Key Components
### Models (`pkg/models/`)
The core data structures:
- **Host** — SSH host with address, credentials, group, tags
- **Key** — SSH key metadata
- **Snippet** — Reusable connection snippets
### Configuration (`pkg/config/`)
Manages application configuration, data directory path, and initialization.
### Storage (`pkg/storage/`)
Handles persistent storage:
- File-based JSON storage per data type
- Export/import with replace and merge strategies
- File permission enforcement (0600)
### CLI Layer (`cmd/hostkeeper/`)
Each command follows a consistent pattern:
1. Parse flags
2. Load config and storage
3. Execute business logic
4. Format output
### TUI (`pkg/tui/`)
Bubble Tea model with:
- Screen-based navigation (extensible for future screens)
- Host list view with styled output
- Keyboard-driven interaction
## Data Flow
### Adding a Host
```
User → hostkeeper add (flags) → parse args → Host model → storage.SaveHost() → JSON file
```
### Connecting to a Host
```
User → hostkeeper connect <name> → findHost() by ID → resolve key → exec native SSH or Go SSH
```
### Export/Import
```
Export: storage → marshal JSON → write file
Import: read file → unmarshal → strategy (merge/replace) → save all
```
## Security
- Credential files stored with `0600` permissions
- SSH key content stored within host credentials
- Direct mode uses Go SSH client (no shell, command execution only)
- Native SSH mode delegates all terminal handling to system SSH
## Future Architecture
- SQLite database for improved query capabilities
- Encrypted credential storage (age/gpg)
- Configuration encryption
- Plugin system for custom authentication methods
-113
View File
@@ -1,113 +0,0 @@
# Installation Guide
## Prerequisites
- **Go 1.21+** — for building from source
- **Git** — for cloning the repository
- **Make** — optional, for build automation
## Installation Methods
### Method 1: Build from Source
```bash
git clone https://git.tukangketik.id/swanadiva/HostKeeper.git
cd hostkeeper
make build
sudo cp bin/hostkeeper /usr/local/bin/
```
### Method 2: Go Install
```bash
go install git.tukangketik.id/swanadiva/hostkeeper/cmd/hostkeeper@latest
```
This installs to `$GOPATH/bin` or `$HOME/go/bin`.
### Method 3: Cross-Platform Build
```bash
./build.sh
```
Binaries will be in the `build/` directory with SHA256 checksums.
## Platform-Specific Instructions
### macOS
```bash
# Install Go via Homebrew
brew install go
# Build and install
git clone https://git.tukangketik.id/swanadiva/HostKeeper.git
cd hostkeeper
make build
cp bin/hostkeeper /usr/local/bin/
```
### Linux (Ubuntu/Debian)
```bash
# Install Go
sudo apt update
sudo apt install golang git make
# Build and install
git clone https://git.tukangketik.id/swanadiva/HostKeeper.git
cd hostkeeper
make build
sudo cp bin/hostkeeper /usr/local/bin/
```
### Windows
```powershell
# Install Go from https://golang.org/dl/
# Clone repository
git clone https://git.tukangketik.id/swanadiva/HostKeeper.git
cd hostkeeper
# Build
go build -o hostkeeper.exe ./cmd/hostkeeper
```
### Termux (Android)
```bash
pkg install golang git make
git clone https://git.tukangketik.id/swanadiva/HostKeeper.git
cd hostkeeper
make build
cp bin/hostkeeper $PREFIX/bin/
```
## Verification
```bash
hostkeeper version
```
Expected output:
```
hostkeeper dev (built: ...)
```
## Troubleshooting
### Command Not Found
Ensure the binary is in your PATH:
```bash
export PATH=$PATH:/usr/local/bin
```
### Build Failures
```bash
make clean
make deps
make build
```
-187
View File
@@ -1,187 +0,0 @@
# Hostkeeper Test Plan — Phase 3
> **Created**: 2025-01-31
> **Purpose**: Comprehensive test coverage for all packages
> **Target Coverage**: 90%+ for all packages
---
## Current Coverage Summary
| Package | Current | Target | Gap |
|---------|---------|--------|-----|
| `internal/errors` | 100% | 100% | — |
| `internal/models` | 91% | 100% | +9% |
| `pkg/crypto` | **0%** | **100%** | +100% |
| `pkg/knownhosts` | **0%** | **100%** | +100% |
| `pkg/ssh` | 60% | 90% | +30% |
| `pkg/storage` | 40% | 90% | +50% |
| `pkg/config` | 22% | 80% | +58% |
| `pkg/tui` | ~40% | 70% | +30% |
| `cmd/hostkeeper` | ~40% | 70% | +30% |
---
## Test File Structure
```
test/
├── crypto/
│ └── crypto_test.go (NEW — 17 scenarios)
├── knownhosts/
│ └── knownhosts_test.go (NEW — 14 scenarios)
├── storage/
│ ├── export_import_test.go (EXISTING)
│ └── json_storage_test.go (NEW — 18 scenarios)
├── config/
│ └── config_test.go (NEW — 10 scenarios)
├── ssh/
│ └── ssh_test.go (EXISTING)
├── tui/
│ ├── theme_test.go (EXISTING)
│ ├── error_banner_test.go (EXISTING)
│ └── responsive_test.go (NEW — 10 scenarios)
├── cmd/
│ └── root_test.go (NEW — 8 scenarios)
├── errors_test.go (EXISTING)
├── models_test.go (EXISTING)
└── integration/
└── integration_test.go (EXISTING)
```
**Total: 7 new test files, ~87 test scenarios**
---
## Scenario Details
### 1. `test/crypto/crypto_test.go` — CRITICAL
| # | Scenario | Input | Expected | Priority |
|---|----------|-------|----------|----------|
| 1.1 | DeriveKey determinism | same password + salt | same key | HIGH |
| 1.2 | DeriveKey password variation | different password | different key | HIGH |
| 1.3 | DeriveKey salt variation | different salt | different key | HIGH |
| 1.4 | DeriveKey empty password | "" | no panic, valid key | MEDIUM |
| 1.5 | Encrypt/Decrypt round-trip | "hello world" | decrypt = original | HIGH |
| 1.6 | Encrypt empty plaintext | "" | decrypt = "" | MEDIUM |
| 1.7 | Encrypt large data | 1MB random bytes | round-trip OK | MEDIUM |
| 1.8 | Encrypt unicode | "こんにちは" | round-trip OK | MEDIUM |
| 1.9 | Encrypt with newlines | "line1\nline2" | round-trip OK | MEDIUM |
| 1.10 | Wrong password | decrypt with wrong pw | ErrDecryptionFailed | HIGH |
| 1.11 | Empty password | decrypt with "" | ErrDecryptionFailed | HIGH |
| 1.12 | IsEncrypted valid ciphertext | base64 ciphertext | true | HIGH |
| 1.13 | IsEncrypted plaintext | "hello" | false | HIGH |
| 1.14 | IsEncrypted empty | "" | false | MEDIUM |
| 1.15 | HashPassword determinism | same pw | same hash | HIGH |
| 1.16 | HashPassword variation | different pw | different hash | HIGH |
| 1.17 | Encrypt randomness | same input, 2 calls | different ciphertext | MEDIUM |
### 2. `test/knownhosts/knownhosts_test.go` — CRITICAL
| # | Scenario | Input | Expected | Priority |
|---|----------|-------|----------|----------|
| 2.1 | New creates file | non-existent path | file created | HIGH |
| 2.2 | New loads existing | existing file | hosts loaded | HIGH |
| 2.3 | Add new host | hostname+port+key | added | HIGH |
| 2.4 | Add duplicate | same host twice | no error, no duplicate | HIGH |
| 2.5 | Get existing | hostname+port | returns HostKey | HIGH |
| 2.6 | Get non-existent | unknown host | nil | MEDIUM |
| 2.7 | Remove existing | hostname+port | removed | HIGH |
| 2.8 | Remove non-existent | unknown host | no error | MEDIUM |
| 2.9 | Verify unknown | new host | (false, nil) TOFU | HIGH |
| 2.10 | Verify known match | correct key | (true, hostKey) | HIGH |
| 2.11 | Verify known mismatch | wrong key | (false, hostKey) MITM | HIGH |
| 2.12 | HostKeyCallback | autoAdd=true | adds unknown hosts | HIGH |
| 2.13 | Persistence | Add → Save → New → Get | found | HIGH |
| 2.14 | Corrupted file | invalid JSON | error | MEDIUM |
### 3. `test/storage/json_storage_test.go` — HIGH
| # | Scenario | Input | Expected | Priority |
|---|----------|-------|----------|----------|
| 3.1 | SaveKeyPair | valid KeyPair | success | HIGH |
| 3.2 | ListKeyPairs | after save | returns saved | HIGH |
| 3.3 | GetKeyPair found | by ID | returns KeyPair | HIGH |
| 3.4 | GetKeyPair not found | unknown ID | error | MEDIUM |
| 3.5 | DeleteKeyPair | by ID | removed | HIGH |
| 3.6 | DeleteKeyPair not found | unknown ID | no error | MEDIUM |
| 3.7 | SaveSnippet | valid Snippet | success | HIGH |
| 3.8 | ListSnippets | after save | returns saved | HIGH |
| 3.9 | GetSnippet found | by ID | returns Snippet | HIGH |
| 3.10 | GetSnippet not found | unknown ID | error | MEDIUM |
| 3.11 | DeleteSnippet | by ID | removed | HIGH |
| 3.12 | DeleteSnippet not found | unknown ID | no error | MEDIUM |
| 3.13 | SetPassword + SaveHost | encrypted storage | file encrypted | HIGH |
| 3.14 | IsDataEncrypted | encrypted file | true | HIGH |
| 3.15 | Wrong password load | decrypt with wrong pw | error | HIGH |
| 3.16 | MergeStrategyMerge | import with merge | keeps existing + adds new | HIGH |
| 3.17 | MergeStrategyReplace | import with replace | overwrites all | HIGH |
| 3.18 | SaveHost empty ID | host with "" ID | generates UUID | MEDIUM |
### 4. `test/config/config_test.go` — MEDIUM
| # | Scenario | Input | Expected | Priority |
|---|----------|-------|----------|----------|
| 4.1 | New first run | no config file | creates default | HIGH |
| 4.2 | New existing | valid config file | loads config | HIGH |
| 4.3 | Save | modify + save | persists | HIGH |
| 4.4 | UpdateAppConfig | change theme | saved | HIGH |
| 4.5 | GetConfigDir | — | valid path | MEDIUM |
| 4.6 | GetDataDir | — | valid path | MEDIUM |
| 4.7 | GetConfigFilePath | — | ends with config.json | MEDIUM |
| 4.8 | GetHostsFilePath | — | ends with hosts.json | MEDIUM |
| 4.9 | GetKeysFilePath | — | ends with keys.json | MEDIUM |
| 4.10 | GetSnippetsFilePath | — | ends with snippets.json | MEDIUM |
### 5. `test/tui/responsive_test.go` — MEDIUM
| # | Scenario | Input | Expected | Priority |
|---|----------|-------|----------|----------|
| 5.1 | WrapFooter short | short string | single line | MEDIUM |
| 5.2 | WrapFooter long | long string | multi-line | MEDIUM |
| 5.3 | WrapFooter empty | "" | "" | LOW |
| 5.4 | ClampWidth over | width > max | clamped to max | MEDIUM |
| 5.5 | ClampWidth under | width < min | clamped to min | MEDIUM |
| 5.6 | ClampWidth in range | min < width < max | unchanged | MEDIUM |
| 5.7 | TruncateStr short | short string | unchanged | MEDIUM |
| 5.8 | TruncateStr long | long string | truncated + … | MEDIUM |
| 5.9 | TruncateStr empty | "" | "" | LOW |
| 5.10 | TruncateStr unicode | "こんにちは世界" | correct width | MEDIUM |
### 6. `test/cmd/root_test.go` — MEDIUM
| # | Scenario | Input | Expected | Priority |
|---|----------|-------|----------|----------|
| 6.1 | RootCmd execute | no args | no error | HIGH |
| 6.2 | Version flag | --version | shows version | MEDIUM |
| 6.3 | FindHost by ID | host ID | returns host | HIGH |
| 6.4 | FindHost by name | host name | returns host | HIGH |
| 6.5 | FindHost by hostname | IP/hostname | returns host | HIGH |
| 6.6 | FindHost not found | unknown | error | MEDIUM |
| 6.7 | NewStorage no password | --password="" | unencrypted | MEDIUM |
| 6.8 | NewStorage with password | --password="x" | encrypted | HIGH |
---
## Execution Order
```
Phase 3, Sprint 1: Security-Critical (pkg/crypto, pkg/knownhosts)
Phase 3, Sprint 2: Core (pkg/storage, pkg/config)
Phase 3, Sprint 3: UI + CLI (pkg/tui, cmd/hostkeeper)
Phase 3, Sprint 4: Integration + Final
```
---
## Success Criteria
- [ ] All tests pass (`go test ./test/...`)
- [ ] Coverage >= 90% for `pkg/crypto`
- [ ] Coverage >= 90% for `pkg/knownhosts`
- [ ] Coverage >= 80% for `pkg/storage`
- [ ] Coverage >= 70% for `pkg/config`
- [ ] Coverage >= 60% for `pkg/tui`
- [ ] No race conditions (`go test -race ./test/...`)
- [ ] Build clean (`go build ./...`)
-151
View File
@@ -1,151 +0,0 @@
# Usage Guide
## Host Management
### Add a Host
Interactive mode:
```bash
hostkeeper add
```
With flags:
```bash
hostkeeper add myserver --host 192.168.1.10 --user admin --password mypass
hostkeeper add myserver --host 10.0.0.1 --port 2222 --user root --key ~/.ssh/id_rsa
hostkeeper add webserver --host example.com --user deploy --password secret --group production --tags web,frontend
```
Flags:
- `--host` — hostname or IP address
- `--port` — SSH port (default: 22)
- `--user` — SSH username
- `--password` — SSH password
- `--key` — path to SSH private key
- `--auth-type` — authentication type (password, key, both)
- `--group` — host group for categorization
- `--tags` — comma-separated tags
- `--notes` — notes about the host
### List Hosts
```bash
hostkeeper list
hostkeeper ls # alias
```
Filter by group or tag:
```bash
hostkeeper list --group production
hostkeeper list --tag web
```
Output formats:
```bash
hostkeeper list --format table # default
hostkeeper list --format json
```
Sort options:
```bash
hostkeeper list --sort name # default
hostkeeper list --sort hostname
hostkeeper list --sort group
```
### Connect to a Host
Connect using system SSH (default, full interactive terminal):
```bash
hostkeeper connect myserver
```
Connect with Go SSH client (direct mode, no interactive shell):
```bash
hostkeeper connect myserver --direct
```
Custom timeout:
```bash
hostkeeper connect myserver --timeout 60
```
### Edit a Host
Interactive mode:
```bash
hostkeeper edit myserver
```
With flags (only update what you specify):
```bash
hostkeeper edit myserver --host 10.0.0.1 --port 2222
hostkeeper edit myserver --user root --name myserver-renamed
hostkeeper edit myserver --group staging --tags backend
```
### Delete a Host
With confirmation prompt:
```bash
hostkeeper delete myserver
hostkeeper rm myserver # alias
```
Skip confirmation:
```bash
hostkeeper delete myserver --force
```
## Data Management
### Export
Export all hosts, keys, and snippets to a JSON file:
```bash
hostkeeper export backup
hostkeeper export backup.json
```
### Import
Import from a previously exported JSON file:
```bash
hostkeeper import backup.json # merge (default)
hostkeeper import backup.json --strategy replace
hostkeeper import backup.json --dry-run # preview only
```
Merge strategies:
- `merge` — keep existing data, add new items (default)
- `replace` — replace all existing data with imported data
## TUI Interface
Launch the interactive terminal UI:
```bash
hostkeeper tui
```
Navigation:
- `↑`/`k` — move up
- `↓`/`j` — move down
- `Enter` — select host
- `q` — quit
## Shell Completion
Generate shell completion scripts:
```bash
hostkeeper completion bash > /etc/bash_completion.d/hostkeeper
hostkeeper completion zsh > /usr/local/share/zsh/site-functions/_hostkeeper
hostkeeper completion fish > ~/.config/fish/completions/hostkeeper.fish
hostkeeper completion powershell > hostkeeper.ps1
```
## Global Flags
- `--config` — path to custom config file
- `-v`/`--verbose` — verbose output (-v for info, -vv for debug)
- `--debug` — enable debug mode
- `--help` — display help