Deadlock u promeni šifre i TERM za PTY
All checks were successful
Tests / unit-tests (push) Successful in 45s
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:
parent
ba801b28ea
commit
3bb8d289af
61
config.go
61
config.go
@ -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$")
|
||||||
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user