a geicko-2 based round robin ranking system designed to test c++ battleship submissions battleship.dunkirk.sh
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: fix padding

+14 -336
-167
AUTH_IMPLEMENTATION.md
··· 1 - # SSH Public Key Authentication - Implementation Summary 2 - 3 - ## ✅ What Was Implemented 4 - 5 - ### 1. Database Schema 6 - Added `users` table to track authenticated users: 7 - - `username` - SSH username (unique) 8 - - `name` - Full name (required during onboarding) 9 - - `bio` - Optional description 10 - - `link` - Optional website/social link 11 - - `public_key` - SSH public key (unique, used for auth) 12 - - `created_at` - Registration timestamp 13 - - `last_login_at` - Last successful login 14 - 15 - **Location:** `internal/storage/database.go` + new `internal/storage/users.go` 16 - 17 - ### 2. SSH Authentication Handler 18 - Implements public key authentication flow: 19 - - Checks if public key is registered 20 - - If registered: verifies username matches and allows access 21 - - If new: checks if username is available 22 - - If username taken: rejects (prevents key reuse) 23 - - If available: flags user for onboarding 24 - 25 - **Location:** `internal/server/auth.go` 26 - 27 - ### 3. Onboarding Flow 28 - Interactive terminal prompt for first-time users: 29 - - Prompts for full name (required) 30 - - Prompts for bio (optional, skip with Enter) 31 - - Prompts for link (optional, skip with Enter) 32 - - Creates user record with their public key 33 - - Subsequent logins skip onboarding 34 - 35 - **Location:** `internal/server/auth.go` + `internal/tui/onboarding.go` 36 - 37 - ### 4. User Profile Pages 38 - Web interface to view user information: 39 - - `/users` - List all registered users 40 - - `/user/{username}` - Individual user profile showing: 41 - - Name, bio, and link 42 - - SSH public key fingerprint 43 - - Game statistics (rating, wins, losses) 44 - - Join date and last login 45 - 46 - **Location:** `internal/server/users.go` 47 - 48 - ### 5. Leaderboard Integration 49 - Updated leaderboard to link usernames to profiles: 50 - - Clicking a username takes you to their profile 51 - - Shows authentication info alongside game stats 52 - 53 - **Location:** `internal/server/web.go` (updated player name links) 54 - 55 - ## 🔐 Security Features 56 - 57 - 1. **Public key only** - No password authentication accepted 58 - 2. **Username ownership** - One public key per username, cannot be changed 59 - 3. **Key uniqueness** - One public key cannot register multiple usernames 60 - 4. **Automatic verification** - Every connection validates the key 61 - 62 - ## 📝 User Experience 63 - 64 - ### First Connection 65 - ```bash 66 - ssh -p 2222 -i ~/.ssh/id_ed25519 alice@localhost 67 - ``` 68 - 69 - **Prompts:** 70 - ``` 71 - 🚢 Welcome to Battleship Arena! 72 - Setting up account for: alice 73 - 74 - What's your full name? (required): Alice Johnson 75 - Bio (optional, press Enter to skip): CS student and battleship enthusiast 76 - Link (optional, press Enter to skip): https://github.com/alice 77 - 78 - ✅ Account created successfully! 79 - You can now upload your battleship AI and compete! 80 - ``` 81 - 82 - ### Subsequent Connections 83 - ```bash 84 - ssh -p 2222 alice@localhost 85 - # → Immediately shows TUI dashboard (no prompts) 86 - ``` 87 - 88 - ### Uploading Files 89 - ```bash 90 - scp -P 2222 memory_functions_alice.cpp alice@localhost:~/ 91 - # → Works with same key authentication 92 - ``` 93 - 94 - ## 🌐 Web Interface 95 - 96 - ### User List (`/users`) 97 - - Grid view of all registered users 98 - - Shows name, username, bio 99 - - Click to view full profile 100 - 101 - ### User Profile (`/user/alice`) 102 - - Full name and username 103 - - Bio and external link (if provided) 104 - - SSH public key fingerprint (SHA256) 105 - - Game statistics (if they've competed) 106 - - Registration and last login timestamps 107 - 108 - ### Leaderboard (`/`) 109 - - Usernames are now clickable links 110 - - Lead to user profile pages 111 - - Shows rating, wins, losses, etc. 112 - 113 - ## 📂 Files Modified/Created 114 - 115 - ### New Files 116 - - `internal/storage/users.go` - User CRUD operations 117 - - `internal/server/auth.go` - SSH authentication handlers 118 - - `internal/server/users.go` - User profile web handlers 119 - - `internal/tui/onboarding.go` - Onboarding TUI (Bubble Tea model) 120 - - `SSH_AUTH.md` - User-facing documentation 121 - 122 - ### Modified Files 123 - - `internal/storage/database.go` - Added users table to schema 124 - - `cmd/battleship-arena/main.go` - Added auth handlers and user routes 125 - - `internal/server/web.go` - Updated player name links to /user/ 126 - 127 - ## 🚀 Testing 128 - 129 - 1. **Start server:** 130 - ```bash 131 - make run 132 - ``` 133 - 134 - 2. **Connect with new user:** 135 - ```bash 136 - ssh -p 2222 newuser@localhost 137 - ``` 138 - 139 - 3. **View users:** 140 - ``` 141 - http://localhost:8081/users 142 - ``` 143 - 144 - 4. **View profile:** 145 - ``` 146 - http://localhost:8081/user/newuser 147 - ``` 148 - 149 - 5. **Try duplicate username:** 150 - ```bash 151 - # With different SSH key, same username → should be rejected 152 - ``` 153 - 154 - ## 💡 Design Decisions 155 - 156 - 1. **Onboarding in terminal** - Users are already in SSH, so keep it simple 157 - 2. **Public key as primary key** - Ensures one key = one account 158 - 3. **Optional bio/link** - Don't force users to provide info they don't want to share 159 - 4. **SHA256 fingerprint display** - More readable than full public key 160 - 5. **Separate /user/ route** - Distinguishes from game stats at /player/ 161 - 162 - ## 🔄 Migration Path 163 - 164 - Existing deployments will need to: 165 - 1. Run migration to add users table (happens automatically on next startup) 166 - 2. Existing SSH users will be prompted for onboarding on next login 167 - 3. No data loss - submission history remains intact
-60
SSH_AUTH.md
··· 1 - # SSH Public Key Authentication 2 - 3 - The Battleship Arena now uses SSH public key authentication for secure, passwordless access. 4 - 5 - ## First-Time Setup 6 - 7 - 1. **Generate an SSH key** (if you don't have one): 8 - ```bash 9 - ssh-keygen -t ed25519 -f ~/.ssh/battleship_arena 10 - ``` 11 - 12 - 2. **Connect for the first time**: 13 - ```bash 14 - ssh -p 2222 -i ~/.ssh/battleship_arena yourname@localhost 15 - ``` 16 - 17 - 3. **Complete onboarding**: 18 - - Enter your full name (required) 19 - - Enter a bio (optional) 20 - - Enter a website/link (optional) 21 - 22 - 4. **Your public key is now registered!** Only you can access this username. 23 - 24 - ## Uploading Your AI 25 - 26 - After registration, upload your battleship AI: 27 - 28 - ```bash 29 - scp -P 2222 -i ~/.ssh/battleship_arena memory_functions_yourname.cpp yourname@localhost:~/ 30 - ``` 31 - 32 - ## User Profiles 33 - 34 - - View your profile: `https://arena.example.com/user/yourname` 35 - - View all users: `https://arena.example.com/users` 36 - - Profiles display: 37 - - Name, bio, and link 38 - - SSH public key fingerprint 39 - - Game statistics (if you've competed) 40 - 41 - ## Security Features 42 - 43 - - ✅ Public key authentication only (no passwords) 44 - - ✅ Username ownership tied to SSH key 45 - - ✅ Keys cannot be reused for different usernames 46 - - ✅ Automatic key verification on every connection 47 - 48 - ## SSH Config 49 - 50 - Add to `~/.ssh/config` for easy access: 51 - 52 - ``` 53 - Host battleship 54 - HostName localhost 55 - Port 2222 56 - User yourname 57 - IdentityFile ~/.ssh/battleship_arena 58 - ``` 59 - 60 - Then simply: `ssh battleship`
-95
STRUCTURE.md
··· 1 - # Battleship Arena - Code Structure 2 - 3 - Refactored into a clean modular architecture with proper separation of concerns. 4 - 5 - ## Directory Structure 6 - 7 - ``` 8 - battleship-arena/ 9 - ├── cmd/ 10 - │ └── battleship-arena/ # Main application entry point 11 - │ └── main.go # Server initialization and routing 12 - ├── internal/ # Private application code 13 - │ ├── runner/ # Match execution and compilation 14 - │ │ ├── runner.go # AI compilation and match running 15 - │ │ └── worker.go # Background submission processor 16 - │ ├── server/ # HTTP/SSH server components 17 - │ │ ├── scp.go # SCP file upload handler 18 - │ │ ├── sftp.go # SFTP file upload handler 19 - │ │ ├── sse.go # Server-Sent Events for live updates 20 - │ │ └── web.go # HTTP handlers and HTML templates 21 - │ ├── storage/ # Data persistence layer 22 - │ │ ├── database.go # SQLite schema and queries 23 - │ │ └── tournament.go # Tournament bracket management 24 - │ └── tui/ # Terminal User Interface 25 - │ └── model.go # Bubble Tea SSH interface 26 - ├── battleship-engine/ # C++ battleship game engine 27 - ├── static/ # Static web assets 28 - ├── go.mod # Go module definition 29 - └── Makefile # Build automation 30 - 31 - ``` 32 - 33 - ## Module Responsibilities 34 - 35 - ### `cmd/battleship-arena` 36 - - Application entry point 37 - - Server initialization (SSH, HTTP, SSE) 38 - - Dependency injection and configuration 39 - - Graceful shutdown handling 40 - 41 - ### `internal/runner` 42 - - **runner.go**: Compiles C++ submissions, generates match binaries, runs head-to-head games 43 - - **worker.go**: Background worker that processes pending submissions in a queue 44 - 45 - ### `internal/server` 46 - - **scp.go**: Validates and handles SCP file uploads from students 47 - - **sftp.go**: SFTP subsystem for file uploads 48 - - **sse.go**: Server-Sent Events for real-time leaderboard updates and progress tracking 49 - - **web.go**: HTTP handlers for leaderboard, player pages, and API endpoints 50 - 51 - ### `internal/storage` 52 - - **database.go**: SQLite schema, CRUD operations, Glicko-2 rating system implementation 53 - - **tournament.go**: Bracket generation, seeding, match scheduling, winner advancement 54 - 55 - ### `internal/tui` 56 - - **model.go**: Bubble Tea terminal interface shown when students SSH in 57 - 58 - ## Key Design Decisions 59 - 60 - 1. **Internal packages**: Use `internal/` to prevent external imports and keep APIs private 61 - 2. **Dependency injection**: Pass configuration (uploadDir, ports) through function parameters rather than globals 62 - 3. **Clean interfaces**: Each module exports only what's needed (capital letters for public functions) 63 - 4. **Separation of concerns**: Storage, presentation, business logic, and transport are cleanly separated 64 - 5. **No circular dependencies**: Dependencies flow downward (cmd → server/runner → storage) 65 - 66 - ## Building & Running 67 - 68 - ```bash 69 - # Build binary 70 - make build 71 - 72 - # Run server 73 - make run 74 - 75 - # Generate SSH host key 76 - make gen-key 77 - 78 - # Clean artifacts 79 - make clean 80 - ``` 81 - 82 - ## Adding Features 83 - 84 - - **New API endpoint**: Add handler to `internal/server/web.go`, register route in `cmd/battleship-arena/main.go` 85 - - **New database table**: Update schema in `storage.InitDB()`, add query functions to `internal/storage/database.go` 86 - - **New match logic**: Modify `internal/runner/runner.go` 87 - - **New TUI screen**: Update model in `internal/tui/model.go` 88 - 89 - ## Testing 90 - 91 - ```bash 92 - go test ./... 93 - ``` 94 - 95 - Currently no tests exist (all packages return `[no test files]`), but the modular structure makes it easy to add unit tests for each package.
battleship-arena

This is a binary file and will not be displayed.

+1 -1
internal/server/auth.go
··· 25 25 func SetConfig(passcode, url string) { 26 26 adminPasscode = passcode 27 27 externalURL = url 28 - log.Printf("✓ Config loaded: passcode=%s..., url=%s", passcode[:10], url) 28 + log.Printf("✓ Config loaded: url=%s\n", url) 29 29 } 30 30 31 31 func PublicKeyAuthHandler(ctx ssh.Context, key ssh.PublicKey) bool {
+13 -13
internal/tui/model.go
··· 149 149 b.WriteString(lipgloss.NewStyle().Bold(true).Render("📤 Your Submissions") + "\n\n") 150 150 151 151 headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("240")) 152 - b.WriteString(headerStyle.Render(fmt.Sprintf("%-35s %-15s %s\n", 153 - "Filename", "Uploaded", "Status"))) 152 + b.WriteString(headerStyle.Render(fmt.Sprintf("%-35s %-15s %s", 153 + "Filename", "Uploaded", "Status")) + "\n") 154 154 155 155 for _, sub := range submissions { 156 156 var statusColor string ··· 169 169 170 170 relTime := formatRelativeTime(sub.UploadTime) 171 171 172 - // Format the line without styles first for proper alignment 172 + // Build the line manually to avoid formatting issues with ANSI codes 173 + line := fmt.Sprintf("%-35s %-15s ", sub.Filename, relTime) 173 174 statusStyled := lipgloss.NewStyle().Foreground(lipgloss.Color(statusColor)).Render(sub.Status) 174 - b.WriteString(fmt.Sprintf("%-35s %-15s %s\n", 175 - sub.Filename, relTime, statusStyled)) 175 + b.WriteString(line + statusStyled + "\n") 176 176 } 177 177 178 178 return b.String() ··· 208 208 for i, entry := range entries { 209 209 rank := fmt.Sprintf("#%d", i+1) 210 210 211 - // Apply color only to the rank 212 - var coloredRank string 211 + // Apply color only to the rank and pad manually 212 + var displayRank string 213 213 if i == 0 { 214 - coloredRank = lipgloss.NewStyle().Foreground(lipgloss.Color("220")).Render(rank) // Gold 214 + displayRank = lipgloss.NewStyle().Foreground(lipgloss.Color("220")).Render(rank) + " " // Gold 215 215 } else if i == 1 { 216 - coloredRank = lipgloss.NewStyle().Foreground(lipgloss.Color("250")).Render(rank) // Silver 216 + displayRank = lipgloss.NewStyle().Foreground(lipgloss.Color("250")).Render(rank) + " " // Silver 217 217 } else if i == 2 { 218 - coloredRank = lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Render(rank) // Bronze 218 + displayRank = lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Render(rank) + " " // Bronze 219 219 } else { 220 - coloredRank = rank 220 + displayRank = fmt.Sprintf("%-4s", rank) 221 221 } 222 222 223 223 // Format line with Glicko-2 rating ± RD 224 224 ratingStr := fmt.Sprintf("%d±%d", entry.Rating, entry.RD) 225 - b.WriteString(fmt.Sprintf("%-4s %-20s %11s %8d %8d %9.2f%% %9.1f\n", 226 - coloredRank, entry.Username, ratingStr, entry.Wins, entry.Losses, entry.WinPct, entry.AvgMoves)) 225 + b.WriteString(fmt.Sprintf("%s %-20s %11s %8d %8d %9.2f%% %9.1f\n", 226 + displayRank, entry.Username, ratingStr, entry.Wins, entry.Losses, entry.WinPct, entry.AvgMoves)) 227 227 } 228 228 229 229 return b.String()