Files
swanadiva c215d68c81 refactor: flatten docs/ — move V2 docs from docs/v2/ to docs/
- git mv docs/v2/*.md docs/ (11 planning documents)
- Remove empty docs/v2/ directory
- Update all references: AGENTS.md, CHANGELOG.md, README.md, ARCHITECTURE.md
- Fix outdated repo tree in ARCHITECTURE.md to match actual structure
2026-07-07 12:04:59 +07:00

12 KiB

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.

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

{
    "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:

// 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.

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.

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

{
    "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.

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.

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.

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)

{
    "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

{
    "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

{
    "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.