chore: V2 planning docs + template discovery

This commit is contained in:
swanadiva
2026-07-07 11:53:16 +07:00
parent 4a8b4a2bc4
commit 8ebdebedc8
31 changed files with 9304 additions and 0 deletions
+391
View File
@@ -0,0 +1,391 @@
# Hostkeeper V2 — Extended Data Models
> **Status**: V2 Planning Complete
> **Last Updated**: 2026-06-29
> **V1 models**: `internal/models/models.go` — frozen, no changes.
---
## 1. Overview
V2 extends V1 models with new entities for groups, vault, port forwarding, workspaces, and sessions. All V1 models remain unchanged. New models are added in `app/backend/models/` (or `internal/models/` with backward-compatible additions).
---
## 2. V1 Models (FROZEN — do not modify)
These exist in `internal/models/models.go` and are used as-is:
- `Host` — SSH host connection configuration
- `AuthConfig` — Authentication configuration (password/key/agent)
- `KeyPair` — SSH key pair
- `Snippet` — Command snippet
- `Profile` — Named configuration profile
- `AppConfig` — Application configuration
- `KnownHost` — Verified host key
---
## 3. V2 Models (NEW)
### 3.1 HostGroup
Nestable group for organizing hosts. Groups can contain other groups and hosts.
```go
type HostGroup struct {
ID string `json:"id" bson:"_id,omitempty"`
Name string `json:"name"`
ParentID *string `json:"parent_id,omitempty"` // nil = root group
Color string `json:"color,omitempty"` // hex color e.g. "#e78a4e"
Icon string `json:"icon,omitempty"` // icon name e.g. "server", "globe"
Order int `json:"order"` // sort order within parent
Children []*HostGroup `json:"children,omitempty"` // populated at runtime, not stored
HostCount int `json:"host_count,omitempty"` // populated at runtime
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
```
**Storage**: `groups.json`
```json
{
"groups": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Production",
"parent_id": null,
"color": "#e78a4e",
"icon": "server",
"order": 0,
"created_at": "2024-06-22T10:00:00Z",
"updated_at": "2024-06-22T10:00:00Z"
}
]
}
```
**Rules**:
- Maximum nesting depth: 5 levels
- Root groups have `parent_id: null`
- Deleting a group moves its hosts to parent (or ungrouped)
- Group names must be unique within the same parent
---
### 3.2 Extended Host
New fields added to the existing `Host` struct:
```go
// V2 additions to Host struct
type HostV2 struct {
// ... all V1 fields ...
GroupID *string `json:"group_id,omitempty"` // reference to HostGroup.ID
IsFavorite bool `json:"is_favorite"` // quick access
Color string `json:"color,omitempty"` // override group color
Order int `json:"order"` // sort order within group
StartupSnippets []string `json:"startup_snippets,omitempty"` // snippet IDs to run on connect
Proxy *ProxyConfig `json:"proxy,omitempty"` // proxy configuration
JumpHost *string `json:"jump_host_id,omitempty"` // jump host ID
Keepalive int `json:"keepalive,omitempty"` // keepalive interval in seconds
}
```
**Note**: V1 Host fields are untouched. V2 fields are added with `omitempty` for backward compatibility.
---
### 3.3 ProxyConfig
Proxy configuration for SSH connections through proxy servers.
```go
type ProxyConfig struct {
Type string `json:"type"` // "socks5", "http", "command"
Host string `json:"host"` // proxy host
Port int `json:"port"` // proxy port
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Command string `json:"command,omitempty"` // for "command" type (netcat-style)
}
```
---
### 3.4 PortForward
SSH port forwarding configuration.
```go
type PortForward struct {
ID string `json:"id" bson:"_id,omitempty"`
Name string `json:"name"`
HostID string `json:"host_id"` // reference to Host.ID
Type string `json:"type"` // "local", "remote", "dynamic"
LocalAddr string `json:"local_addr"` // e.g. "127.0.0.1:8080"
RemoteAddr string `json:"remote_addr"` // e.g. "localhost:3000"
Status string `json:"status"` // "stopped", "running", "error"
AutoStart bool `json:"auto_start"` // start tunnel on app launch
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
```
**Storage**: `forwards.json`
```json
{
"forwards": [
{
"id": "forward-uuid",
"name": "Local Redis",
"host_id": "host-uuid",
"type": "local",
"local_addr": "127.0.0.1:6379",
"remote_addr": "localhost:6379",
"status": "stopped",
"auto_start": false,
"created_at": "2024-06-22T10:00:00Z",
"updated_at": "2024-06-22T10:00:00Z"
}
]
}
```
**Forward Types**:
- **Local**: `ssh -L localPort:remoteHost:remotePort` — forward local port to remote
- **Remote**: `ssh -R remotePort:localHost:localPort` — forward remote port to local
- **Dynamic**: `ssh -D localPort` — SOCKS5 proxy through SSH
---
### 3.5 Workspace
Group multiple terminal sessions in a layout.
```go
type Workspace struct {
ID string `json:"id" bson:"_id,omitempty"`
Name string `json:"name"`
Layout string `json:"layout"` // "single", "split-h", "split-v", "grid-2x2"
Tabs []Tab `json:"tabs"` // terminal tabs in this workspace
IsActive bool `json:"is_active"` // currently active workspace
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type Tab struct {
ID string `json:"id"`
HostID string `json:"host_id"`
Type string `json:"type"` // "terminal", "sftp"
Title string `json:"title"` // display title (host name)
Cols int `json:"cols"`
Rows int `json:"rows"`
}
```
**Storage**: `workspaces.json`
**Layout Types**:
- `single` — one terminal, full width
- `split-h` — two terminals side by side (horizontal split)
- `split-v` — two terminals stacked (vertical split)
- `grid-2x2` — four terminals in a 2x2 grid
---
### 3.6 Session (Runtime only, not persisted)
Active terminal session tracking.
```go
type Session struct {
ID string `json:"id"`
HostID string `json:"host_id"`
StartedAt time.Time `json:"started_at"`
LastActive time.Time `json:"last_active"`
Status string `json:"status"` // "connecting", "connected", "disconnected", "error"
Cols int `json:"cols"`
Rows int `json:"rows"`
}
```
**Note**: Sessions are in-memory only. Not stored to disk.
---
### 3.7 VaultStatus (Runtime only)
Vault lock/unlock state tracking.
```go
type VaultStatus struct {
Locked bool `json:"locked"`
EncryptionEnabled bool `json:"encryption_enabled"`
AutoLockEnabled bool `json:"auto_lock_enabled"`
AutoLockMinutes int `json:"auto_lock_minutes"`
LastActivity time.Time `json:"last_activity"`
UnlockAttempts int `json:"unlock_attempts"` // failed attempts
LockedUntil *time.Time `json:"locked_until,omitempty"` // temporary lock after too many attempts
}
```
**Auto-lock rules**:
- Default: lock after 5 minutes of inactivity
- After 5 failed unlock attempts: temporary lock for 30 seconds
- Lock on system sleep/suspend
- Lock on window minimize (configurable)
---
## 4. Storage Schema
### File Structure
```
~/.hostkeeper/
├── hosts.json # V1 + V2 host data
├── keys.json # V1 + V2 key pairs
├── snippets.json # V1 + V2 snippets
├── groups.json # V2: host groups
├── forwards.json # V2: port forwards
├── workspaces.json # V2: workspaces
├── config.json # V1 + V2 app config
├── known_hosts.json # V1 + V2 known hosts
├── profiles.json # V2: profiles (if separated from config)
└── sessions.json # V2: session history (optional)
```
### JSON Schema Examples
#### hosts.json (extended)
```json
{
"hosts": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Production Server",
"hostname": "192.168.1.100",
"port": 22,
"username": "admin",
"auth": {
"type": "key",
"key_id": "key-uuid"
},
"group_id": "group-uuid",
"tags": ["production", "linux"],
"is_favorite": true,
"color": "#e78a4e",
"order": 0,
"notes": "Main production server",
"startup_snippets": ["snippet-uuid-1"],
"proxy": {
"type": "socks5",
"host": "10.0.0.1",
"port": 1080
},
"jump_host_id": "jump-host-uuid",
"keepalive": 60,
"created_at": "2024-06-22T10:00:00Z",
"updated_at": "2024-06-22T10:00:00Z",
"last_used_at": "2024-06-22T15:30:00Z"
}
]
}
```
#### groups.json
```json
{
"groups": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Production",
"parent_id": null,
"color": "#e78a4e",
"icon": "server",
"order": 0,
"created_at": "2024-06-22T10:00:00Z",
"updated_at": "2024-06-22T10:00:00Z"
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Web Servers",
"parent_id": "550e8400-e29b-41d4-a716-446655440000",
"color": "#a9b665",
"icon": "globe",
"order": 0,
"created_at": "2024-06-22T10:00:00Z",
"updated_at": "2024-06-22T10:00:00Z"
}
]
}
```
#### forwards.json
```json
{
"forwards": [
{
"id": "forward-uuid",
"name": "Local Redis",
"host_id": "host-uuid",
"type": "local",
"local_addr": "127.0.0.1:6379",
"remote_addr": "localhost:6379",
"status": "stopped",
"auto_start": false,
"created_at": "2024-06-22T10:00:00Z",
"updated_at": "2024-06-22T10:00:00Z"
}
]
}
```
---
## 5. Relationships
```
HostGroup (1) ──── (*) Host
│ │
│ parent_id │ group_id
│ (self-ref) │ key_id
│ │ startup_snippets (*)
│ │ jump_host_id
│ │
HostGroup (1) ──── (*) HostGroup (children)
└── max depth: 5
Host (1) ──── (*) PortForward
└── host_id
Workspace (1) ──── (*) Tab
└── tabs[].host_id
Host (1) ──── (*) Snippet (via startup_snippets)
└── startup_snippets[] = snippet IDs
```
---
## 6. Migration from V1
V2 data is backward-compatible with V1. New fields are optional (`omitempty`).
**Migration steps** (automatic on first V2 launch):
1. Read existing `hosts.json` — V2 fields default to zero values
2. Create `groups.json` if not exists — empty groups
3. Create `forwards.json` if not exists — empty forwards
4. Create `workspaces.json` if not exists — empty workspaces
5. Update `config.json` — add V2 config fields
**No data loss**: V1 data is preserved. V2 fields are additive.