From 3bb8d289afa930d75a11ca404e0c960e786840fb Mon Sep 17 00:00:00 2001 From: djuka Date: Wed, 18 Feb 2026 06:14:28 +0000 Subject: [PATCH] =?UTF-8?q?Deadlock=20u=20promeni=20=C5=A1ifre=20i=20TERM?= =?UTF-8?q?=20za=20PTY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ispravljen deadlock: SetPassword zaključa mu pa pozove save() koji opet zaključa mu. Razdvojeno u saveLocked() (bez lock-a) i save() - Vraćeno normalno ponašanje kursora (Claude Code sam upravlja) - PTY okruženje: TERM=xterm-256color, COLORTERM=truecolor Co-Authored-By: Claude Opus 4.6 --- config.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ pty_session.go | 17 +++++--------- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/config.go b/config.go index fda41e0..0414a86 100644 --- a/config.go +++ b/config.go @@ -4,6 +4,10 @@ import ( "encoding/json" "fmt" "os" + "strings" + "sync" + + "golang.org/x/crypto/bcrypt" ) type APIConfig struct { @@ -19,6 +23,9 @@ type Config struct { Password string `json:"password"` SessionSecret string `json:"session_secret"` API APIConfig `json:"api"` + + path string // putanja do config fajla (ne serijalizuje se) + mu sync.Mutex // zaštita za upis } func LoadConfig(path string) (*Config, error) { @@ -51,5 +58,59 @@ func LoadConfig(path string) (*Config, error) { return nil, fmt.Errorf("session_secret is required") } + cfg.path = path + + // Ako šifra nije hešovana, heširaj je i sačuvaj + if !isHashed(cfg.Password) { + hashed, err := bcrypt.GenerateFromPassword([]byte(cfg.Password), bcrypt.DefaultCost) + if err != nil { + return nil, fmt.Errorf("hashing password: %w", err) + } + cfg.Password = string(hashed) + if err := cfg.save(); err != nil { + return nil, fmt.Errorf("saving hashed password: %w", err) + } + } + return &cfg, nil } + +// CheckPassword proverava da li se data šifra poklapa sa hešom. +func (c *Config) CheckPassword(password string) bool { + return bcrypt.CompareHashAndPassword([]byte(c.Password), []byte(password)) == nil +} + +// SetPassword postavlja novu šifru (hešuje i čuva u config). +func (c *Config) SetPassword(newPassword string) error { + hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) + if err != nil { + return fmt.Errorf("hashing password: %w", err) + } + c.mu.Lock() + c.Password = string(hashed) + err = c.saveLocked() + c.mu.Unlock() + return err +} + +// save upisuje trenutni config nazad u fajl (pozivalac mora držati c.mu). +func (c *Config) saveLocked() error { + data, err := json.MarshalIndent(c, "", " ") + if err != nil { + return fmt.Errorf("marshaling config: %w", err) + } + data = append(data, '\n') + return os.WriteFile(c.path, data, 0600) +} + +// save upisuje trenutni config nazad u fajl. +func (c *Config) save() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.saveLocked() +} + +// isHashed proverava da li je string bcrypt heš. +func isHashed(s string) bool { + return strings.HasPrefix(s, "$2a$") || strings.HasPrefix(s, "$2b$") +} diff --git a/pty_session.go b/pty_session.go index fc4c515..5e1a938 100644 --- a/pty_session.go +++ b/pty_session.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "fmt" "log" "os" @@ -12,9 +11,6 @@ import ( "github.com/creack/pty" ) -// ANSI escape sequence to hide cursor — Claude Code's Ink TUI sends this. -// We strip it so xterm.js cursor stays visible. -var cursorHideSeq = []byte("\x1b[?25l") const ( outputBufferSize = 128 * 1024 // 128KB ring buffer for replay @@ -82,10 +78,15 @@ func SpawnPTY(projectDir string) (*PTYSession, error) { cmd.Dir = projectDir // Filter env vars to prevent nested session detection - cmd.Env = filterEnvMulti(os.Environ(), []string{ + env := filterEnvMulti(os.Environ(), []string{ "CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT", + "TERM", + "COLORTERM", }) + // Set proper terminal type for full color and cursor support + env = append(env, "TERM=xterm-256color", "COLORTERM=truecolor") + cmd.Env = env ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: 24, Cols: 80}) if err != nil { @@ -123,12 +124,6 @@ func (s *PTYSession) readLoop() { data := make([]byte, n) copy(data, buf[:n]) - // Strip cursor hide sequence so xterm.js cursor stays visible - data = bytes.ReplaceAll(data, cursorHideSeq, []byte{}) - if len(data) == 0 { - continue - } - s.buffer.Write(data) s.mu.Lock()