Deadlock u promeni šifre i TERM za PTY
All checks were successful
Tests / unit-tests (push) Successful in 45s

- 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 <noreply@anthropic.com>
This commit is contained in:
djuka 2026-02-18 06:14:28 +00:00
parent ba801b28ea
commit 3bb8d289af
2 changed files with 67 additions and 11 deletions

View File

@ -4,6 +4,10 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"strings"
"sync"
"golang.org/x/crypto/bcrypt"
) )
type APIConfig struct { type APIConfig struct {
@ -19,6 +23,9 @@ type Config struct {
Password string `json:"password"` Password string `json:"password"`
SessionSecret string `json:"session_secret"` SessionSecret string `json:"session_secret"`
API APIConfig `json:"api"` 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) { func LoadConfig(path string) (*Config, error) {
@ -51,5 +58,59 @@ func LoadConfig(path string) (*Config, error) {
return nil, fmt.Errorf("session_secret is required") 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 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$")
}

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"log" "log"
"os" "os"
@ -12,9 +11,6 @@ import (
"github.com/creack/pty" "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 ( const (
outputBufferSize = 128 * 1024 // 128KB ring buffer for replay outputBufferSize = 128 * 1024 // 128KB ring buffer for replay
@ -82,10 +78,15 @@ func SpawnPTY(projectDir string) (*PTYSession, error) {
cmd.Dir = projectDir cmd.Dir = projectDir
// Filter env vars to prevent nested session detection // Filter env vars to prevent nested session detection
cmd.Env = filterEnvMulti(os.Environ(), []string{ env := filterEnvMulti(os.Environ(), []string{
"CLAUDECODE", "CLAUDECODE",
"CLAUDE_CODE_ENTRYPOINT", "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}) ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: 24, Cols: 80})
if err != nil { if err != nil {
@ -123,12 +124,6 @@ func (s *PTYSession) readLoop() {
data := make([]byte, n) data := make([]byte, n)
copy(data, buf[: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.buffer.Write(data)
s.mu.Lock() s.mu.Lock()