# Hostkeeper V2 — Architecture > **Status**: Migrasi React → HTMX > **Last Updated**: 2026-07-07 > **Dokumen arsitektur baru**: [ARCHITECTURE_HTMX.md](ARCHITECTURE_HTMX.md) > **V1 (existing)**: CLI/TUI SSH/SFTP manager — frozen, no changes. --- ## ⚠️ Keputusan Arsitektur Hostkeeper V2 menggunakan **GoFiber + HTMX**, **bukan React/SPA**. Keputusan ini diambil karena: 1. Developer lebih nyaman dengan Go daripada JavaScript 2. ~90% kode adalah Go + HTML template 3. Hanya ~410 baris JS yang diperlukan (xterm.js, clipboard, toggle) 4. Build lebih sederhana — tanpa Vite, npm, TypeScript 5. Bisa berjalan di browser biasa tanpa Electron **Dokumen arsitektur lengkap ada di** [`ARCHITECTURE_HTMX.md`](ARCHITECTURE_HTMX.md). --- ### Communication Flow ``` User interaction (keyboard, mouse) │ ▼ React component (event handler) │ ▼ API client (fetch / WebSocket) │ ▼ GoFiber HTTP server (localhost:randomPort) │ ├── REST handlers → pkg/storage → JSON files └── WebSocket handlers → pkg/ssh → remote server ``` --- ## 2. Technology Stack ### Backend (Go) | Component | Library | Purpose | |-----------|---------|---------| | HTTP server | `gofiber/fiber/v2` | REST API, static file serving | | WebSocket | `gofiber/contrib/websocket` | Terminal streaming, real-time updates | | WebSocket engine | `fasthttp/websocket` | Underlying WS protocol (fork of gorilla) | | SSH client | `golang.org/x/crypto/ssh` | Remote SSH connections (reuse V1 pkg/ssh) | | SFTP client | `github.com/pkg/sftp` | File transfer (reuse V1 pkg/sftp) | | Crypto | `crypto/aes`, `crypto/cipher` | AES-256-GCM encryption (reuse V1 pkg/crypto) | | Storage | Standard `os` + `encoding/json` | JSON file persistence (reuse V1 pkg/storage) | | Config | `github.com/spf13/viper` | App configuration (reuse V1 pkg/config) | | UUID | `github.com/google/uuid` | Unique IDs for entities | | Routers | `github.com/gofiber/fiber/v2` | HTTP routing | | Middleware | GoFiber built-in | CORS, compress, recover, logger | ### Frontend (React/TypeScript) | Component | Library | Purpose | |-----------|---------|---------| | Framework | React 19 + TypeScript | UI components | | Build tool | Vite | Fast development + production build | | Terminal | xterm.js + addons | Terminal emulation (fit, web-links, search) | | State | Zustand | Lightweight state management | | Routing | React Router v7 | Screen navigation | | Styling | TailwindCSS + CSS variables | Theming (dark/light/contrast) | | Icons | Lucide React | Consistent icon set | | Notifications | Sonner | Toast notifications | ### Desktop (Electron) | Component | Library | Purpose | |-----------|---------|---------| | Shell | Electron 33 | Native desktop wrapper | | Packaging | electron-builder | Build .dmg, .exe, .AppImage | | Auto-update | electron-updater | In-app updates | | Keychain | Node.js safeStorage | OS keychain integration | | IPC | Electron IPC | Go backend <-> Electron communication | ### Mobile | Component | Library | Purpose | |-----------|---------|---------| | Go library | gomobile bind | Compile Go to .aar/.xcframework | | UI shell | React Native WebView | Display web UI on mobile | | Native code | Java (Android) / Swift (iOS) | Minimal WebView wrapper | --- ## 3. Directory Structure ``` hostkeeper/ ├── cmd/ # V1 CLI (FROZEN) │ └── hostkeeper/ ├── pkg/ # V1 shared packages (FROZEN) │ ├── ssh/ │ ├── sftp/ │ ├── crypto/ │ ├── storage/ │ ├── config/ │ ├── knownhosts/ │ └── tui/ ├── internal/ # V1 internal packages (FROZEN) │ ├── models/ │ └── errors/ ├── test/ # V1 tests (FROZEN) │ ├── app/ # V2 GUI (NEW) │ ├── backend/ # Go HTTP server │ │ ├── main.go # Entry point, fiber server setup │ │ ├── go.mod # Go module for backend │ │ ├── bridge/ # REST API handlers │ │ │ ├── hosts.go │ │ │ ├── keys.go │ │ │ ├── snippets.go │ │ │ ├── groups.go │ │ │ ├── portforward.go │ │ │ ├── sftp.go │ │ │ ├── config.go │ │ │ ├── vault.go │ │ │ └── export.go │ │ ├── ws/ # WebSocket handlers │ │ │ ├── terminal.go # SSH terminal streaming │ │ │ └── session.go # Session management │ │ └── middleware/ # Custom middleware │ │ └── middleware.go # (optional overrides) │ │ │ ├── frontend/ # React web UI │ │ ├── src/ │ │ │ ├── components/ # Reusable UI components │ │ │ ├── screens/ # Page-level components │ │ │ ├── hooks/ # Custom React hooks │ │ │ ├── stores/ # Zustand state stores │ │ │ ├── api/ # API client functions │ │ │ └── styles/ # CSS, themes, variables │ │ ├── index.html │ │ ├── vite.config.ts │ │ ├── tailwind.config.js │ │ ├── tsconfig.json │ │ └── package.json │ │ │ └── electron/ # Electron shell │ ├── main.ts # Main process │ ├── preload.ts # Preload script (IPC bridge) │ ├── build.ts # Build configuration │ ├── package.json │ └── electron-builder.yml │ ├── mobile/ # V2 Mobile (NEW) │ ├── android/ # Android WebView app │ │ ├── app/ │ │ ├── build.gradle │ │ └── ... │ ├── ios/ # iOS WKWebView app │ │ ├── Hostkeeper/ │ │ ├── Hostkeeper.xcodeproj │ │ └── ... │ └── gomobile/ # Go mobile compilation │ ├── build.sh │ └── ... │ ├── scripts/ # Build scripts │ ├── build.sh # Full build (Go + FE + Electron) │ ├── build-go.sh # Go cross-compile │ ├── build-fe.sh # Frontend build │ ├── build-electron.sh # Electron package │ └── build-mobile.sh # Mobile build │ ├── docs/ # Documentation (V2 planning) │ ├── PROGRESS.md # Status tracker (read this first) │ ├── ARCHITECTURE.md # This file │ ├── API.md # REST API + WebSocket spec │ ├── DATA_MODELS.md # Extended data models │ ├── UI_COMPONENTS.md # React component tree │ ├── UI_TERMIUS_REFERENCE.md # Termius visual reference │ ├── SPRINT_PLAN.md # Sprint-by-sprint task breakdown │ ├── BUILD_SYSTEM.md # Build pipeline │ ├── PERFORMANCE.md # Performance & stability │ └── TIMELINE.md # Project timeline │ ├── CHANGELOG.md ├── README.md ├── AGENTS.md # AI session checkpoint ├── v1/ # V1 CLI/TUI (FROZEN) │ ├── cmd/hostkeeper/ │ ├── internal/ │ ├── pkg/ │ ├── test/ │ ├── docs/ # V1 docs │ ├── go.mod │ └── Makefile ├── template/ # React UI template (Lumina System) └── app/ # V2 app (scaffolding not started) ├── backend/ ├── frontend/ └── electron/ ``` --- ## 4. Process Model ### Startup Sequence ``` 1. User double-clicks Hostkeeper.app (Electron) 2. Electron main process starts 3. Electron spawns Go binary as child process 4. Go binary starts GoFiber server on localhost:randomPort 5. Go serves frontend dist (embedded in Electron) 6. Electron loads web UI from localhost:PORT 7. Web UI renders vault unlock screen 8. User enters password -> Go unlocks vault (decrypts data) 9. User sees host list screen -> ready to use ``` ### Shutdown Sequence ``` 1. User closes window / Cmd+Q 2. Electron sends SIGTERM to Go child process 3. Go receives signal -> starts graceful shutdown 4. Go disconnects all active SSH sessions 5. Go saves any pending state 6. Go closes fiber server 7. Go exits 8. Electron exits ``` ### Development Mode ``` 1. Terminal: cd app/backend && go run . 2. Terminal: cd app/frontend && npm run dev 3. Go server at localhost:8080 4. Frontend dev server at localhost:5173 (Vite HMR) 5. Frontend proxies API requests to localhost:8080 6. Open browser at localhost:5173 ``` --- ## 5. Data Flow ### REST API Request Flow ``` React component → useQuery/useMutation (React Query or manual fetch) → API client (fetch wrapper) → HTTP request to GoFiber (localhost:PORT/api/...) → GoFiber router -> handler function → Handler calls pkg/storage (read/write JSON) → Response sent back through the chain ``` ### Terminal WebSocket Flow ``` User opens terminal tab → React: XTermWrapper mounts → XTermWrapper opens WebSocket to ws://localhost:PORT/ws/terminal?host_id=xxx&cols=80&rows=24 → GoFiber WebSocket handler accepts connection → Go: SSH client connects to remote host (reuse pkg/ssh) → SSH session created, PTY requested → SSH stdout goroutine -> reads SSH output -> c.WriteMessage(BinaryMessage, data) -> xterm.js receives data -> renders in terminal -> xterm.js onKey -> c.WriteMessage(BinaryMessage, input) -> Go receives input -> session.Stdin.Write(input) -> SSH remote ``` ### SFTP File Transfer Flow ``` User drags file to remote pane → React: FileList onDrop handler → API client: POST /api/sftp/upload (multipart/form-data) → Go: bridge/sftp.go receives upload → Go: pkg/sftp client writes to remote → Response: { "success": true } → React: refresh remote file list ``` --- ## 6. Security Architecture ### Encryption Model | Layer | Method | Implementation | |-------|--------|----------------| | Data at rest | AES-256-GCM | `pkg/crypto/crypto.go` (reuse V1) | | Key derivation | PBKDF2 (100k iterations) | `pkg/crypto/crypto.go` (reuse V1) | | Password storage | SHA-256 hash | `pkg/config/config.go` (reuse V1) | | Known hosts | TOFU verification | `pkg/knownhosts/knownhosts.go` (reuse V1) | | Transport | localhost only | No TLS needed (same machine) | | Export | AES-256-GCM with password | `pkg/storage/json_storage.go` (reuse V1) | ### Vault Flow ``` App start -> vault locked -> prompt password -> PBKDF2 derive key from password -> Try decrypt hosts.json -> Success -> vault unlocked, data accessible -> Fail -> wrong password, show error -> Vault locked -> no data accessible -> Auto-lock after inactivity timer ``` ### OS Keychain Integration | Platform | Library | Purpose | |----------|---------|---------| | macOS | `security` CLI or `go-keychain` | Store vault master key | | Windows | `go-credential` | Store vault master key | | Linux | `dbus` Secret Service | Store vault master key | | Electron | `safeStorage` API | Store vault master key | --- ## 7. WebSocket Protocol ### Terminal WebSocket **Connection**: `ws://localhost:PORT/ws/terminal?host_id=xxx&cols=80&rows=24` **Message types** (binary frames): | Direction | Type | Content | |-----------|------|---------| | Client → Server | Input | Raw keystroke bytes | | Server → Client | Output | Terminal output bytes | | Client → Server | Resize | JSON: `{ "type": "resize", "cols": 120, "rows": 40 }` | | Server → Client | Status | JSON: `{ "type": "status", "state": "connected" }` | | Server → Client | Error | JSON: `{ "type": "error", "message": "..." }` | **Keepalive**: Client sends ping every 30s, server responds with pong. --- ## 8. Build System ### Development ```bash # Terminal 1: Go backend cd app/backend go run . # Terminal 2: Frontend with hot reload cd app/frontend npm run dev # Browser opens at http://localhost:5173 ``` ### Production Build ```bash # Full build ./scripts/build.sh # Output: # dist/hostkeeper-mac-arm64.dmg # dist/hostkeeper-win-x64.exe # dist/hostkeeper-linux-x64.AppImage ``` ### Cross-Compilation Matrix | Platform | GOOS | GOARCH | Output | |----------|------|--------|--------| | macOS ARM64 | darwin | arm64 | hostkeeper-mac-arm64 | | macOS x64 | darwin | amd64 | hostkeeper-mac-x64 | | Windows x64 | windows | amd64 | hostkeeper-win-x64.exe | | Linux x64 | linux | amd64 | hostkeeper-linux-x64 | | Linux ARM64 | linux | arm64 | hostkeeper-linux-arm64 | | Android ARM64 | android | arm64 | hostkeeper-android.aar | | iOS ARM64 | ios | arm64 | hostkeeper-ios.xcframework | --- ## 9. Error Handling ### Go Backend Errors ```go // All API errors follow this format: { "error": "Human-readable error message", "code": "MACHINE_READABLE_CODE", "details": "Optional technical details" } // HTTP status codes: // 200 - Success // 201 - Created // 400 - Bad Request (validation error) // 401 - Unauthorized (vault locked) // 404 - Not Found // 409 - Conflict (duplicate) // 500 - Internal Server Error ``` ### Frontend Error Handling - Network errors: automatic retry with exponential backoff (3 retries) - API errors: toast notification with error message - WebSocket errors: reconnect button in terminal, auto-reconnect 3 times - Validation errors: inline field-level error messages --- ## 10. Testing Strategy | Layer | Tool | Coverage Target | |-------|------|----------------| | Go unit tests | `go test` | 80%+ | | Go integration tests | `go test -tags=integration` | Key flows | | React unit tests | Vitest + React Testing Library | 70%+ | | React E2E tests | Playwright | Critical flows | | Manual testing | All platforms | Every sprint | ### Test Commands ```bash # Go backend tests cd app/backend && go test ./... # Frontend tests cd app/frontend && npm test # Full test suite make test # Race condition check go test -race ./app/backend/... ```