3.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
# Build
go build .
# Build with injected encryption key (production)
go build -ldflags "-X gotermix/internals.fileEncKeyHex=$(openssl rand -hex 32)" .
# Build with env-var key
export GOTERMINAL_ENC="your64hexchars"
go build -ldflags "-X gotermix/internals.fileEncKeyHex=${GOTERMINAL_ENC}" .
# Run (dev)
./gotermix
./gotermix -addr 0.0.0.0:8443 -nopw
# Change credentials (app must restart to pick up)
./gotermix -setlogin newuser newpassword
# Store custom TLS cert (validates before storing, exits after)
./gotermix -cert /etc/ssl/my.crt -certkey /etc/ssl/my.key
./gotermix -certreset
# Tests — none exist yet
go vet ./...
Architecture
Entry point: main.go (5 lines). All logic in internals/ package. Web assets in internals/web/ (embedded via //go:embed).
Session model
/ always creates a new PTY-backed shell session (hex ID 32 chars). /s/<id> reconnects to existing session. Multiple browser tabs can share one session — all see the same PTY output via broadcast. The session ID is embedded in the served HTML via strings.NewReplacer replacing [[SESSION_ID]] and [[AUTHED]] literals in the shellPageHTML const. Sessions are reaped after 24h idle (10-min ticker). Rolling 1MB replay buffer lets new tab connections catch up on history.
Transport
HTTPS only. Self-signed cert auto-generated in memory on startup unless custom cert paths are stored (encrypted) in gws-creds.json. TLS handshake errors from browsers rejecting self-signed certs are suppressed via tlsHandshakeFilter. WebSocket (/ws/<id>) carries raw PTY bytes as binary frames; resize events as JSON text frames.
Auth flow
- Browser POSTs credentials to
/auth - Server checks against
storedCreds(SHA-256 × 50,000 rounds + salt, constant-time compare) - On success: sets
gws_authcookie (HttpOnly, Secure, SameSite=Lax, 12h) containing HMAC-SHA256 timestamp token - All subsequent routes (
/ws/,/upload,/download) callisAuthed()which validates the token -nopwflag bypasses all auth checks (nopwMode = true)
Credential & key storage
gws-creds.json(next to binary): AES-256-GCM encrypted JSON with username, salt, hash, optional cert paths- Encryption key priority: (1) build-time
-ldflags "-X gotermix/internals.fileEncKeyHex=...", (2)gws.keyfile next to binary, (3) auto-generated and written togws.key - Default creds if file missing or unreadable: user
ivor/Silv3rSw0rd!
File transfer
- Upload: browser POSTs multipart to
/uploadwithsidfield. Server writes to OS temp file, then injectsmv <tmp> <dest>directly into the PTY shell — file lands with the shell's effective user permissions. - Download: GET
/download?path=...→http.ServeFileafterfilepath.Clean. Absolute paths used directly; relative paths anchored toinitialCwd(cwd at startup).
Frontend
UI lives in internals/web/shell.html (~670 lines, embedded at build time). Favicon in internals/web/favicon.svg. Uses xterm.js + FitAddon from CDN. Keyboard handling: capture-phase listener blocks all Ctrl+key browser shortcuts; xterm's attachCustomKeyEventHandler handles Ctrl+Shift+C (copy), Ctrl+V (paste via Clipboard API). [[SESSION_ID]] and [[AUTHED]] placeholders replaced by serveTerminalPage via strings.NewReplacer.
External dependencies
github.com/creack/pty— PTY allocation and resize (pty.Start,pty.Setsize)github.com/gorilla/websocket— WebSocket upgrader and framing
Routes
| Path | Handler |
|---|---|
/ |
New session, serve terminal page |
/s/<32-hex-id> |
Reconnect to specific session |
/ws/<32-hex-id> |
WebSocket: PTY I/O + resize |
/auth |
POST: credential check, set cookie |
/upload |
POST multipart: inject mv into PTY |
/download |
GET ?path=: ServeFile |
/favicon.svg |
Inline SVG terminal icon |