# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash # Build (static — no glibc dep, runs on NixOS / Alpine / any Linux) CGO_ENABLED=0 go build . # Build with injected encryption key (production) CGO_ENABLED=0 go build -ldflags "-X gotermix/internals.fileEncKeyHex=$(openssl rand -hex 32)" . # Build with env-var key export GOTERMINAL_ENC="your64hexchars" CGO_ENABLED=0 go build -ldflags "-X gotermix/internals.fileEncKeyHex=${GOTERMINAL_ENC}" . # Cross-compile for Linux amd64 from any OS CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build . # 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/` 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/`) carries raw PTY bytes as binary frames; resize events as JSON text frames. ### Auth flow 1. Browser POSTs credentials to `/auth` 2. Server checks against `storedCreds` (SHA-256 × 50,000 rounds + salt, constant-time compare) 3. On success: sets `gws_auth` cookie (HttpOnly, Secure, SameSite=Lax, 12h) containing HMAC-SHA256 timestamp token 4. All subsequent routes (`/ws/`, `/upload`, `/download`) call `isAuthed()` which validates the token 5. `-nopw` flag 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.key` file next to binary, (3) auto-generated and written to `gws.key` - Default creds if file missing or unreadable: user `ivor` / `Silv3rSw0rd!` ### File transfer - **Upload**: browser POSTs multipart to `/upload` with `sid` field. Server writes to OS temp file, then injects `mv ` directly into the PTY shell — file lands with the shell's effective user permissions. - **Download**: GET `/download?path=...` → `http.ServeFile` after `filepath.Clean`. Absolute paths used directly; relative paths anchored to `initialCwd` (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 |