v0.11.0 harden persistence and prepare game replays

This commit is contained in:
2026-09-07 14:39:00 -04:00
parent 21dea47694
commit 5d2be4abb2
42 changed files with 3591 additions and 693 deletions
+56 -32
View File
@@ -2,7 +2,7 @@
## Prerequisites
- Go 1.24+
- Go 1.26+
- Stockfish in PATH
- SQLite3
- Git
@@ -14,7 +14,7 @@
git clone https://github.com/lixenwraith/chess
cd chess
go build ./cmd/chess-server
go build ./cmd/chess-client
go build ./cmd/chess-client-cli
```
## Running
@@ -29,20 +29,30 @@ go build ./cmd/chess-client
- `-storage-path`: SQLite database file path (enables persistence and authentication)
- `-pid`: PID file path for process tracking
- `-pid-lock`: Enable exclusive locking (requires -pid)
- `-log-level`: `debug`, `info`, `warn`, or `error` (default: `info`)
- `-log-http`: Enable API and web request logs (default: `true`)
- `-finished-game-ttl`: How long terminal games stay in memory (default: `1h`; `0` disables eviction)
- `-web-api-url`: Browser-visible API origin for the embedded web client; useful when its public origin differs from the listen address
### Modes
```bash
# In-memory only (no persistence or auth)
./chessd
./chess-server
# With persistence and authentication
./chessd -storage-path ./db/chess.db
./chess-server -storage-path ./db/chess.db
# Development with all features
./chessd -dev -storage-path chess.db -pid /tmp/chessd.pid -serve
./chess-server -dev -storage-path chess.db -pid /tmp/chess-server.pid -serve
# Detailed persistence, engine-queue, cleanup, and request logs
./chess-server -dev -storage-path chess.db -serve -log-level debug -log-http=true
# Web UI is public at one origin while the API is exposed at another
./chess-server -serve -web-api-url https://api.example.test
# Initialize database with user tables
./chessd db init -path chess.db
./chess-server db init -path chess.db
```
## Database Management
@@ -50,60 +60,64 @@ go build ./cmd/chess-client
### Schema Initialization
```bash
# Create all tables (users, games, moves)
./chessd db init -path chess.db
./chess-server db init -path chess.db
```
### User Management CLI
```bash
# Add user with password
./chessd db user add -path chess.db -username alice -password SecurePass123
./chess-server db user add -path chess.db -username alice -password SecurePass123
# Add user with email
./chessd db user add -path chess.db -username bob -email bob@example.com -password BobPass456
./chess-server db user add -path chess.db -username bob -email bob@example.com -password BobPass456
# Interactive password input
./chessd db user add -path chess.db -username charlie -interactive
./chess-server db user add -path chess.db -username charlie -interactive
# List all users
./chessd db user list -path chess.db
./chess-server db user list -path chess.db
# Update password
./chessd db user set-password -path chess.db -username alice -password NewPass789
./chess-server db user set-password -path chess.db -username alice -password NewPass789
# Update email
./chessd db user set-email -path chess.db -username alice -email newemail@example.com
./chess-server db user set-email -path chess.db -username alice -email newemail@example.com
# Update username
./chessd db user set-username -path chess.db -current alice -new alice2
./chess-server db user set-username -path chess.db -current alice -new alice2
# Import with existing Argon2 hash
./chessd db user set-hash -path chess.db -username alice -hash '$argon2id$v=19$m=65536,t=3,p=2$...'
./chess-server db user set-hash -path chess.db -username alice -hash '$argon2id$v=19$m=65536,t=3,p=2$...'
# Delete user
./chessd db user delete -path chess.db -username alice
./chess-server db user delete -path chess.db -username alice
```
### Game Query CLI
```bash
# Query all games
./chessd db query -path chess.db -gameId "*"
./chess-server db query -path chess.db -gameId "*"
# Query games for specific user
./chessd db query -path chess.db -playerId "550e8400-e29b-41d4-a716-446655440000"
./chess-server db query -path chess.db -playerId "550e8400-e29b-41d4-a716-446655440000"
# Query specific game
./chessd db query -path chess.db -gameId "a1b2c3d4-e5f6-7890-1234-567890abcdef"
./chess-server db query -path chess.db -gameId "a1b2c3d4-e5f6-7890-1234-567890abcdef"
# Delete database (destructive)
./chessd db delete -path chess.db
./chess-server db delete -path chess.db
```
## Authentication Configuration
### JWT Secret Management
- **Production**: Cryptographically secure 32-byte secret generated on startup
- **Production**: A cryptographically secure 32-byte secret is generated on
startup. This intentionally invalidates JWTs after a restart even though the
SQLite session rows remain; configuring a stable deployment secret is tracked
in `doc/todo.md`.
- **Development** (`-dev`): Fixed secret for testing consistency
- **Sessions**: Valid for 7 days, renewed on each login
- **Sessions**: Stored for 7 days and renewed on each login; effective token
lifetime is also bounded by signing-key rotation
### Password Requirements
- Minimum 8 characters
@@ -171,6 +185,12 @@ See [test documentation](../test/README.md) for comprehensive test suites coveri
# Test real-time game updates via long-polling
./test/test-longpoll.sh
# Unit, migration, and persistence tests
go test ./...
# Concurrency checks for the state/persistence boundary
go test -race ./internal/server/storage ./internal/server/service
```
## Configuration
@@ -180,10 +200,10 @@ See [test documentation](../test/README.md) for comprehensive test suites coveri
- Worker count: 2 (internal/processor/processor.go)
- Queue capacity: 100 (internal/processor/queue.go)
- Min search time: 100ms (internal/processor/processor.go)
- Write queue: 1000 operations (internal/storage/storage.go)
- DB connections: 25 max, 5 idle (internal/storage/storage.go)
- Write queue: 1000 operations (internal/server/storage/storage.go)
- DB connections: 8 max, 4 idle (internal/server/storage/storage.go)
- JWT expiration: 7 days (internal/service/user.go)
- Long-poll timeout: 25 seconds (internal/service/waiter.go)
- Long-poll timeout: 30 seconds (internal/server/service/waiter.go)
- Long-poll channel buffer: 1 (internal/service/waiter.go)
### Authentication Configuration
@@ -193,11 +213,13 @@ See [test documentation](../test/README.md) for comprehensive test suites coveri
- Hash algorithm: Argon2id (memory-hard, side-channel resistant)
### Storage Configuration
- WAL mode enabled in development for concurrency
- Foreign key constraints enforced
- Async write pattern for games with 2-second drain on shutdown
- WAL mode and NORMAL synchronous mode enabled on every connection
- Foreign key constraints and a five-second busy timeout enabled on every connection
- Async write pattern for games; shutdown drains every accepted write
- Replay reads wait for prior queued writes and use one read transaction
- Synchronous writes for user operations (data consistency)
- Degradation to memory-only on write failures
- Registration capacity/eviction, user creation, and initial session are atomic
- A full queue or write failure degrades to memory-only and is visible in logs and `/health`
- Case-insensitive collation for usernames and emails
### Rate Limiting Configuration
@@ -249,6 +271,8 @@ See [test documentation](../test/README.md) for comprehensive test suites coveri
- No password recovery mechanism
- No email verification for registration
- Fixed worker pool size for engine calculations
- No real-time game updates (polling required)
- Long-polling limited to 25 seconds per request
- REST API only
- No push-based game updates (30-second long-polling is used)
- Live games are not rehydrated after restart; persisted games are currently replay-only
- Database history has no automatic retention policy
- Curated-game metadata and replay controls are deferred to [Replay Implementation Tasks](./todo.md)
- REST API only