Sprint 6: JSON file persistence + real SSH terminal + V1 crypto/knownhosts integration

- go.mod replace directive for git.tukangketik.id/swanadiva/hostkeeper → ../../v1
- store/ package: JSON file persistence for hosts, snippets, keys, settings
  (~/Library/Application Support/hostkeeper/v2/)
- model SSH fields: Hostname, Port, Username, AuthType, Password, KeyID
- sshconn/ package: real SSH client via golang.org/x/crypto/ssh
- Terminal WS: real SSH connection with fallback to mock shell
- Dynamic host list in terminal (server-rendered JSON script tag)
- All mock data defaults removed from model packages
- Settings persist via store/settings.go
This commit is contained in:
swanadiva
2026-07-07 13:53:50 +07:00
parent 04d9cfb096
commit fd7361aa14
21 changed files with 746 additions and 306 deletions
+94
View File
@@ -0,0 +1,94 @@
package store
import (
"time"
"git.tukangketik.id/swanadiva/hostkeeper/v2/internal/model"
)
type KeyStore struct {
keys []model.Key
}
func NewKeyStore() *KeyStore {
s := &KeyStore{}
mu.RLock()
readFile("keys.json", &s.keys)
mu.RUnlock()
if s.keys == nil {
s.keys = []model.Key{}
}
return s
}
func (s *KeyStore) All() []model.Key {
mu.RLock()
defer mu.RUnlock()
return s.keys
}
func (s *KeyStore) Search(q string) []model.Key {
mu.RLock()
defer mu.RUnlock()
if q == "" {
return s.keys
}
var res []model.Key
for _, k := range s.keys {
if contains(k.Name, q) || contains(k.Username, q) || contains(k.URL, q) {
res = append(res, k)
}
}
return res
}
func (s *KeyStore) Add(name, username, url, passphrase string) model.Key {
mu.Lock()
defer mu.Unlock()
k := model.Key{
ID: randID(),
Name: name,
Username: username,
URL: url,
Passphrase: passphrase,
Strength: calcStrength(passphrase),
CreatedAt: time.Now().Format("Jan 2, 2006"),
}
s.keys = append(s.keys, k)
writeFile("keys.json", &s.keys)
return k
}
func (s *KeyStore) Delete(id string) {
mu.Lock()
defer mu.Unlock()
for i, k := range s.keys {
if k.ID == id {
s.keys = append(s.keys[:i], s.keys[i+1:]...)
writeFile("keys.json", &s.keys)
return
}
}
}
func calcStrength(p string) string {
l, d, u, s := 0, 0, 0, 0
for _, c := range p {
switch {
case c >= 'a' && c <= 'z':
l++
case c >= 'A' && c <= 'Z':
u++
case c >= '0' && c <= '9':
d++
default:
s++
}
}
if len(p) >= 12 && u >= 1 && d >= 1 && s >= 1 {
return "secure"
}
if len(p) >= 8 && u+d+s >= 2 {
return "moderate"
}
return "weak"
}