docs: Add Arbiter 2.x task directory and Gemini consultation records

WHAT WAS DONE:
- Created docs/tasks/arbiter-2x/ with README and implementation guide
- Created docs/reference/gemini-consultations/ for AI partner records
- Documented complete Arbiter 2.x architecture and implementation plan

FILES ADDED:
- docs/tasks/arbiter-2x/README.md (overview, phases, gotchas)
- docs/tasks/arbiter-2x/IMPLEMENTATION-GUIDE.md (complete technical spec)
- docs/reference/gemini-consultations/2026-03-31-arbiter-whitelist-architecture.md
- docs/reference/gemini-consultations/2026-03-31-arbiter-implementation-details.md

GEMINI CONSULTATIONS:
Preserved complete Gemini AI architectural consultation from March 31, 2026.
Includes:
- Initial architecture consultation (unified app vs microservices)
- Database schema design (PostgreSQL with indexes)
- Minecraft account linking flow (Discord /link command)
- Pterodactyl API integration (File Management API)
- Complete code examples (Mojang validation, file write, cron sync)

IMPLEMENTATION GUIDE INCLUDES:
- 5-phase implementation plan with checklists
- PostgreSQL schema with indexes for 500-user scale
- Production-ready code snippets (pg pool, Mojang API, Panel API)
- Critical gotchas (Content-Type, UUID dashes, HTTP 412)
- Hourly cron reconciliation logic
- Error handling and rate limiting strategies

WHY:
Task #90 is Tier 1 soft launch blocker. This documentation provides
complete blueprint for implementing subscription-driven whitelist
system. All architectural decisions validated by Gemini AI.

NEXT STEPS:
- Phase 1: PostgreSQL database setup
- Phase 2: Core functions (Mojang, Panel API)
- Phase 3: Discord /link command
- Phase 4: Sync system (event-driven + cron)
- Phase 5: Admin panel and testing

Signed-off-by: The Golden Chronicler <claude@firefrostgaming.com>
This commit is contained in:
Claude (Chronicler #35)
2026-03-31 22:47:49 +00:00
parent c7461946da
commit 01a262d030
4 changed files with 906 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
# Response to Gemini - Arbiter 2.x Architecture
Hi Gemini! 👋
Thank you SO much for this comprehensive architectural guidance! This is exactly what we needed.
## Decisions Made
We're moving forward with your recommendations:
**Unified Access Manager (Arbiter 2.x)** - Single Node.js application
**PostgreSQL** - Migrating from SQLite for reliability
**Discord `/link` slash command** - Mojang API validation
**Hybrid sync strategy** - Event-driven push + hourly reconciliation cron
**Pterodactyl File Management API** - Abandoning WebSocket console commands
Your reasoning on each point is solid, especially the "single point of failure = single point of success" philosophy for remote RV operations. If the app is up, everything works. Perfect.
---
## Yes, Please Give Us More Details!
> "Would you like to explore the specific payload structures required for the Pterodactyl File Management API to ensure seamless file overwriting?"
**Absolutely YES!** We need the implementation details for:
### 1. Pterodactyl File Management API Payloads
**Specifically:**
- How to write `whitelist.json` via the API (exact endpoint, headers, payload structure)
- How to trigger `whitelist reload` command after file write
- Error handling (what if file write fails? what if server offline?)
- Rate limiting concerns (writing to 13 servers simultaneously)
### 2. Auto-Discovery Details
**How do we:**
- Query Panel API for all servers
- Identify which physical node (TX1 Dallas vs NC1 Charlotte) each server is on
- Filter out excluded servers (FoundryVTT, Hytale)
- Handle servers that come online/offline dynamically
### 3. Master Whitelist JSON Format
**What should the generated `whitelist.json` look like?**
- Just usernames? `["Steve", "Alex"]`
- Or full format with UUIDs? `[{"uuid": "...", "name": "Steve"}]`
- Does Minecraft server care about format differences?
### 4. Database Schema Refinements
You provided the core tables. **Should we add:**
- `whitelist_sync_log` table to track when each server was last synced?
- `failed_sync_attempts` table for retry logic?
- Indexes for performance at 500 subscriber scale?
- Any other tables we're missing?
### 5. Migration Path Code Examples
**If you have code examples for:**
- PostgreSQL connection setup in Node.js (best practices)
- Mojang API validation with error handling
- Pterodactyl API client class structure
- Cron job setup for periodic reconciliation
**That would be incredibly helpful!**
---
## Additional Context If Needed
**Current Pterodactyl Panel version:** v1.12.1
**Node.js version:** 20 LTS
**Database:** Currently SQLite, migrating to PostgreSQL 15+
**Server count:** 13 game servers (will grow to 20-30 by September 2027)
**Expected subscriber count:** 500 by September 2027
We're building this to be bulletproof for remote operations where we can't SSH to servers and manually fix things.
---
## Thank You!
Your architectural guidance has been invaluable. The monorepo design you helped us with earlier today is already paying dividends, and this Arbiter 2.x architecture feels like the right foundation for scaling to 500 subscribers.
We really appreciate you being our architectural partner on this journey!
— Michael & Claude (The Golden Chronicler) 💙🔥❄️
---
**P.S.** If there are any gotchas or "watch out for this!" warnings about Pterodactyl Panel API or Minecraft whitelist file formats, we'd love to hear them. Remote debugging is painful!

View File

@@ -0,0 +1,308 @@
# Gemini Architectural Consultation: Arbiter + Whitelist Manager Integration
Hi Gemini! 👋
Michael (The Wizard) and Claude (The Golden Chronicler #50) here. We need your architectural expertise on a critical integration between two services that's more complex than we initially realized.
---
## Context: What We're Building
**Firefrost Gaming** - Subscription-based modded Minecraft server hosting platform
- 6 subscription tiers: Awakened ($1), Elemental ($5), Knight ($10), Master ($15), Legend ($20), Sovereign ($50)
- ~13 game servers across 2 physical nodes (TX1 Dallas, NC1 Charlotte)
- Goal: September 2027 remote RV operations (fully automated, low-maintenance)
**The Core Flow:**
1. User subscribes via Paymenter (Stripe payments)
2. **Arbiter** assigns Discord roles based on tier
3. User links Minecraft account (somehow - TBD)
4. **Whitelist Manager** grants server access based on subscription
5. User joins whitelisted Minecraft servers
---
## Current State: Two Broken/Incomplete Services
### Service 1: The Arbiter (Discord OAuth Bot)
**What it currently does:**
- Receives Paymenter subscription webhooks
- Assigns Discord roles based on subscription tier
- Manages role mapping via admin panel
- Ghost CMS integration (purpose unclear)
- Email notifications
**Current architecture:**
- Node.js + Express
- SQLite database (schema unknown - need to check)
- Discord.js bot
- Admin OAuth panel (Trinity-only access)
**What it does NOT do:**
- Store Minecraft usernames
- Communicate with Whitelist Manager
- Handle subscription cancellations (Arbiter 2.1 feature, not yet deployed)
**Code location:** `firefrost-services/services/arbiter/`
---
### Service 2: Whitelist Manager (Server Access Control)
**What it currently does (poorly):**
- Lists servers from Pterodactyl Panel API
- Groups servers by hardcoded name keywords (BROKEN - servers constantly change)
- Checks whitelist status via WebSocket console commands (unreliable)
- Allows manual add/remove players per-server or bulk
**Current architecture:**
- Python + Flask
- No database (in-memory activity log only)
- HTTP Basic Auth
- WebSocket connections to game servers
**What's BROKEN:**
- Server grouping uses name keywords instead of Panel node allocation
- Status detection fails (shows "UNKNOWN" for all servers)
- No concept of "master whitelist"
- No integration with subscriptions
- Purely manual operation
**Code location:** `firefrost-services/services/whitelist-manager/`
---
## What Michael Actually Needs
### The Real Requirements (Just Discovered):
1. **Subscription-Driven Access**
- ANY paid subscriber (even $1 Awakened tier) gets whitelisted
- Happens automatically once Minecraft account is linked
- Cancellation removes whitelist access (with grace period - Arbiter 2.1)
2. **Master Whitelist Concept**
- Single source of truth for approved players
- All Minecraft servers sync from this master list
- When whitelist enabled on server → master list auto-syncs
3. **Auto-Discovery**
- Query Pterodactyl Panel for current servers
- Group by actual node allocation (not name patterns)
- Adapt as servers added/removed
4. **Panel API Integration**
- Detect whitelist enabled/disabled from server properties
- Toggle whitelist via Panel API (not console commands)
- Reliable status detection
5. **Minecraft Account Linking**
- Users link Minecraft username to Discord account
- Stored permanently (survives subscription changes)
- Validated against Mojang API?
---
## The Big Architectural Questions
We need your expertise on:
### Question 1: Data Storage Architecture
**Where should the Minecraft username → Discord ID mapping live?**
**Option A - Arbiter Owns It:**
- Arbiter SQLite database stores: `discord_id`, `minecraft_username`, `subscription_tier`
- Whitelist Manager queries Arbiter's API: `GET /api/subscribers` → returns list of active whitelisted players
- Pro: Single source of truth
- Con: Tight coupling, Whitelist Manager can't work independently
**Option B - Whitelist Manager Owns It:**
- Whitelist Manager has its own database
- Arbiter sends webhooks on subscribe/cancel/link: `POST /api/player-update`
- Pro: Loose coupling, independent services
- Con: Data duplication, sync issues
**Option C - Shared Database:**
- PostgreSQL database both services connect to
- Shared schema: `users`, `subscriptions`, `minecraft_accounts`
- Pro: No API overhead, consistent data
- Con: Tight coupling at DB layer, harder to deploy separately
**Option D - Something else we haven't thought of?**
**What do you recommend for remote RV operations reliability?**
---
### Question 2: Minecraft Account Linking Flow
**How should users link their Minecraft account?**
**Current unknowns:**
- Where does linking happen? (Discord bot command? Web form? Ghost CMS?)
- How do we validate Minecraft usernames? (Mojang API? Just accept any string?)
- Can users link multiple Minecraft accounts? (family subscriptions?)
- What if they change their Minecraft name?
**What's the industry-standard approach for Discord → Minecraft linking?**
---
### Question 3: Arbiter 2.x vs Arbiter 2.1
**Current plan:**
- Arbiter 2.1 adds subscription cancellation + grace period handling
- Whitelist Manager is separate service
**Should we instead build Arbiter 2.x?**
- Arbiter 2.x = combined service (Discord roles + Whitelist management)
- Single service handles entire subscription → server access flow
- Simpler deployment, fewer moving parts
**Or keep them separate?**
- Pro: Single responsibility, easier to test/debug
- Con: More services to manage, integration complexity
**What makes sense for a two-person team running this from an RV?**
---
### Question 4: Real-Time Sync Strategy
**When a user subscribes, how quickly should they get whitelisted?**
**Scenario:**
1. User subscribes via Paymenter ($5/month Elemental tier)
2. Paymenter webhook → Arbiter
3. Arbiter assigns Discord role
4. User links Minecraft account (username: "Steve")
5. **← THIS STEP:** How does "Steve" get added to all 13 game servers?
**Option A - Immediate Push:**
- Arbiter/Whitelist Manager immediately runs `whitelist add Steve` on all 13 servers
- Fast, but what if servers are offline?
**Option B - Periodic Sync:**
- Cron job every 5 minutes syncs master whitelist to all servers
- Slower (up to 5min delay), but more reliable
**Option C - On-Demand:**
- Servers pull whitelist on startup
- Manual "Force Sync" button in admin panel
- Slowest, but most resilient
**What's the right balance for production?**
---
### Question 5: Pterodactyl Panel v1.12.1 Integration
**Current Whitelist Manager uses:**
- WebSocket console commands: `whitelist on`, `whitelist list`
- Unreliable, times out, returns "unknown"
**Should we instead use Panel API?**
- Does Panel v1.12.1 API expose server whitelist status?
- Can we toggle whitelist via API instead of console?
- What about bulk operations (sync to 13 servers)?
**What's the proper way to integrate with Pterodactyl Panel for whitelist management?**
---
## Current Code We Have
**Arbiter:**
- Full source in `firefrost-services/services/arbiter/`
- SQLite database (schema in `src/database.js`)
- Discord bot + OAuth admin panel
- Paymenter webhook handler
- Email service (SMTP)
**Whitelist Manager:**
- Full source in `firefrost-services/services/whitelist-manager/`
- Flask app, no database
- Panel API client
- WebSocket console integration
- Basic auth admin panel
**We can share specific code if needed!**
---
## What We Need From You
### Primary Deliverable:
**Recommended Architecture** for the integrated system:
1. **Data storage strategy** (Option A/B/C/D from Question 1)
2. **Service structure** (Keep separate? Merge into Arbiter 2.x? Microservices?)
3. **Minecraft linking flow** (Discord bot command? Web form? API?)
4. **Whitelist sync strategy** (Real-time push? Periodic sync? On-demand?)
5. **Panel API integration** (Replace WebSocket with API calls? Hybrid?)
### Bonus If You Have Time:
- Database schema for the recommended approach
- API contract between Arbiter ↔ Whitelist Manager (if separate)
- Failure modes and recovery strategies (server offline, Panel API down, etc.)
- Migration path from current broken system to new architecture
---
## Constraints & Context
**Technical:**
- Pterodactyl Panel v1.12.1 (latest)
- Discord.js v14
- Python 3.11 (Whitelist Manager)
- Node.js 20 (Arbiter)
- Infrastructure: Debian 12, systemd services
**Team:**
- Two-person team (Michael + Meg)
- Will be operating remotely from RV (September 2027)
- Need reliable, low-maintenance automation
- Can't SSH to servers to fix things manually while traveling
**Scale:**
- Current: ~13 game servers, 2 physical nodes
- Target: 500 subscribers by September 2027
- 6 subscription tiers
- Unknown number of concurrent Minecraft players
**Philosophy:**
- "We Don't Kick People Out" - payment failures = downgrade, not removal
- Subscribers expect instant access after payment
- Whitelist is per-server (some servers whitelisted, some public)
---
## Questions For Us
If you need clarification on:
- Current Arbiter database schema
- Paymenter webhook payload format
- Panel API response structure
- Discord bot command preferences
- Any technical details
Just ask! We're here and ready to provide more context.
---
Thanks for being our architectural partner, Gemini! Your monorepo guidance earlier today was invaluable. Looking forward to your thoughts on this integration challenge.
— Michael & Claude (The Golden Chronicler) 💙🔥❄️
---
**P.S.** We're also wondering if this should be:
- **Arbiter 2.1** (just add cancellation handling, keep Whitelist Manager separate)
- **Arbiter 2.x** (major rewrite, absorb Whitelist Manager functionality)
- **Completely new service** (Arbiter stays Discord-only, new "Access Manager" handles whitelisting)
Your architectural instincts on service boundaries would be hugely helpful!

View File

@@ -0,0 +1,329 @@
# Gemini's Arbiter 2.x Implementation Guide
## Complete Technical Specification
Date: 2026-03-31
Source: Gemini AI Architectural Consultation
Status: READY TO IMPLEMENT
---
## 1. Pterodactyl File Management API
### Writing whitelist.json
**Endpoint:** `POST /api/client/servers/{server_identifier}/files/write?file=whitelist.json`
**Headers:**
- `Authorization: Bearer <Client_API_Key>`
- `Accept: application/json`
- `Content-Type: text/plain`**CRITICAL: Must be text/plain, not application/json!**
**Body:** Raw stringified JSON array (not wrapped in JSON object)
```javascript
const fileContent = JSON.stringify(whitelistArray, null, 2);
// Send as raw text body
```
### Triggering Reload Command
**Endpoint:** `POST /api/client/servers/{server_identifier}/command`
**Headers:**
- `Authorization: Bearer <Client_API_Key>`
- `Content-Type: application/json`
- `Accept: application/json`
**Body:**
```json
{"command": "whitelist reload"}
```
### Error Handling - CRITICAL!
**HTTP 412 (Server Offline)** is EXPECTED and SAFE:
- File write succeeds even if server offline
- Command fails with 412 = server will use new file on next boot
- DO NOT treat this as an error!
**Rate Limiting:**
- Do NOT use `Promise.all()` for bulk syncs
- Process servers sequentially or in small batches
- Prevents overwhelming Panel API
---
## 2. Auto-Discovery - Application API
**Use Application API, not Client API** for discovery!
**Endpoint:** `GET /api/application/servers?include=allocations,node,nest`
**Headers:** `Authorization: Bearer <Application_API_Key>`
**Filtering Logic:**
1. Check `attributes.nest.attributes.name` === "Minecraft"
2. Check `attributes.node.attributes.name` for physical location (TX1/NC1)
3. Extract `identifier` (8-char string for Client API calls)
**Exclude servers:**
- Filter out by nest type (FoundryVTT is different nest)
- OR check server name against EXCLUDED_SERVERS list
---
## 3. Whitelist JSON Format - EXACT SPECIFICATION
**CRITICAL:** Minecraft requires UUIDs WITH DASHES since 1.8+
**Correct format:**
```json
[
{
"uuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5",
"name": "Notch"
},
{
"uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"name": "Steve"
}
]
```
**Why UUIDs matter:**
- Username-only format forces server to query Mojang on boot
- Slows startup
- Can fail if Mojang API rate-limits server IP
- UUIDs are permanent, usernames can change
**GOTCHA:** Mojang API returns UUIDs WITHOUT dashes - must format before saving!
---
## 4. Database Schema Refinements
### Add Indexes for 500-User Scale
```sql
CREATE INDEX idx_users_discord_id ON users(discord_id);
CREATE INDEX idx_subscriptions_status ON subscriptions(status);
```
### New Table: Sync Tracking
```sql
CREATE TABLE server_sync_log (
server_identifier VARCHAR(50) PRIMARY KEY,
last_successful_sync TIMESTAMP,
last_error TEXT,
is_online BOOLEAN DEFAULT true
);
```
**Purpose:** Debug "Why isn't Steve whitelisted on Dallas node?"
---
## 5. Code Examples - Production Ready
### A. PostgreSQL Connection (using pg pool)
```javascript
// database.js
const { Pool } = require('pg');
const pool = new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
max: 20, // Max connections for 500 user scale
idleTimeoutMillis: 30000
});
module.exports = {
query: (text, params) => pool.query(text, params),
};
```
### B. Mojang API Validation + UUID Formatting
```javascript
// mojang.js
function formatUUID(uuidStr) {
// Converts "069a79f444e94726a5befca90e38aaf5"
// to "069a79f4-44e9-4726-a5be-fca90e38aaf5"
return `${uuidStr.slice(0, 8)}-${uuidStr.slice(8, 12)}-${uuidStr.slice(12, 16)}-${uuidStr.slice(16, 20)}-${uuidStr.slice(20)}`;
}
async function validateMinecraftUser(username) {
try {
const res = await fetch(`https://api.mojang.com/users/profiles/minecraft/${username}`);
// 204 or 404 = user not found
if (res.status === 204 || res.status === 404) return null;
if (!res.ok) throw new Error(`Mojang API error: ${res.status}`);
const data = await res.json();
return {
name: data.name, // Correct capitalization from Mojang
uuid: formatUUID(data.id) // ADD DASHES!
};
} catch (error) {
console.error("Failed to validate Minecraft user:", error);
return null;
}
}
```
### C. Pterodactyl File Write
```javascript
// panel.js
async function writeWhitelistFile(serverIdentifier, whitelistArray) {
const fileContent = JSON.stringify(whitelistArray, null, 2);
const endpoint = `${process.env.PANEL_URL}/api/client/servers/${serverIdentifier}/files/write?file=whitelist.json`;
const res = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PANEL_CLIENT_KEY}`,
'Accept': 'application/json',
'Content-Type': 'text/plain' // MUST be text/plain!
},
body: fileContent
});
if (!res.ok) throw new Error(`Failed to write file: ${res.statusText}`);
return true;
}
```
### D. Pterodactyl Reload Command (Safe Fail)
```javascript
// panel.js (continued)
async function reloadWhitelistCommand(serverIdentifier) {
const endpoint = `${process.env.PANEL_URL}/api/client/servers/${serverIdentifier}/command`;
const res = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PANEL_CLIENT_KEY}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ command: "whitelist reload" })
});
// 412 = server offline, this is FINE!
if (res.status === 412) {
console.log(`[${serverIdentifier}] is offline. File saved for next boot.`);
return true;
}
if (!res.ok) throw new Error(`Command failed: ${res.statusText}`);
return true;
}
```
### E. Hourly Cron Reconciliation
```javascript
// sync.js
const cron = require('node-cron');
const db = require('./database');
const panel = require('./panel');
// Runs at minute 0 past every hour (1:00, 2:00, 3:00, etc.)
cron.schedule('0 * * * *', async () => {
console.log("Starting hourly whitelist reconciliation...");
// 1. Fetch Master Whitelist from Database
const { rows: players } = await db.query(
`SELECT minecraft_username as name, minecraft_uuid as uuid
FROM users
JOIN subscriptions ON users.discord_id = subscriptions.discord_id
WHERE subscriptions.status = 'active'`
);
// 2. Fetch Active Servers via Auto-Discovery
const servers = await panel.getServers();
// 3. Sync Sequentially (prevents rate limiting)
for (const server of servers) {
try {
await panel.writeWhitelistFile(server.identifier, players);
await panel.reloadWhitelistCommand(server.identifier);
// Log successful sync
await db.query(
"UPDATE server_sync_log SET last_successful_sync = NOW() WHERE server_identifier = $1",
[server.identifier]
);
} catch (err) {
console.error(`Sync failed for ${server.identifier}:`, err);
// Log failed sync
await db.query(
"UPDATE server_sync_log SET last_error = $1 WHERE server_identifier = $2",
[err.message, server.identifier]
);
}
}
console.log("Hourly reconciliation complete.");
});
```
---
## Key Gotchas & Warnings
1. **Content-Type MUST be text/plain** for file write endpoint (not application/json)
2. **UUIDs MUST include dashes** in whitelist.json (Mojang returns without dashes)
3. **HTTP 412 is not an error** - server offline, file saved for next boot
4. **Sequential processing** prevents rate limiting (don't use Promise.all)
5. **Application API for discovery**, Client API for file operations
6. **Mojang API can rate-limit** - validate once during /link, store permanently
---
## Implementation Order
**Phase 1: Database Migration**
1. Set up PostgreSQL
2. Create tables with indexes
3. Migrate existing Arbiter data
**Phase 2: Core Functions**
1. Database connection pool
2. Mojang validation
3. Pterodactyl file write/command functions
**Phase 3: Discord Integration**
1. `/link` slash command
2. Auto-DM new subscribers
3. Role assignment webhook handler
**Phase 4: Sync System**
1. Event-driven sync (on /link, on subscribe)
2. Hourly cron reconciliation
3. Sync logging for debugging
**Phase 5: Admin Panel**
1. View sync status
2. Manual sync trigger
3. View linked accounts
---
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️
**Implementation Status:** READY TO BUILD
**Next Step:** Create Task #90 - Arbiter 2.x Implementation

View File

@@ -0,0 +1,177 @@
# Task #90: Arbiter 2.x - Unified Access Manager
**Status:** OPEN
**Priority:** Tier 1 - Soft Launch Blocker
**Estimated Time:** 20-30 hours (5 phases)
**Created:** March 31, 2026
**Architecture Validated By:** Gemini AI
---
## Overview
Complete rewrite merging Arbiter (Discord role management) + Whitelist Manager (server access control) into single Node.js application with PostgreSQL backend.
**Goal:** Subscription-driven Minecraft server whitelist access - user subscribes → links Minecraft account → automatically whitelisted on all servers.
---
## Quick Links
- **Implementation Guide:** `IMPLEMENTATION-GUIDE.md` (complete technical spec with code examples)
- **Architecture Decision:** `../../reference/gemini-consultations/2026-03-31-arbiter-whitelist-architecture.md`
- **Implementation Details:** `../../reference/gemini-consultations/2026-03-31-arbiter-implementation-details.md`
- **Main Task:** `../../core/tasks.md#90`
---
## The Flow
1. User subscribes via Paymenter (any tier, even $1 Awakened)
2. Paymenter webhook → Arbiter assigns Discord role
3. User runs `/link <minecraft_username>` in Discord
4. Arbiter validates via Mojang API, gets UUID
5. Stores in PostgreSQL: discord_id, minecraft_username, minecraft_uuid
6. Master whitelist auto-syncs to all whitelisted servers
7. User can now join any Minecraft server
---
## Why Rewrite?
**Current Whitelist Manager is broken:**
- Hardcoded server name keywords (fails when servers change)
- WebSocket console commands (unreliable, returns "UNKNOWN")
- No subscription integration
- No master whitelist concept
- Manual-only operation
**Current Arbiter doesn't:**
- Store Minecraft usernames
- Communicate with Whitelist Manager
- Handle subscription-driven whitelist access
---
## Architecture Decisions
All validated by Gemini AI consultation (March 31, 2026):
**Single unified app** - Arbiter 2.x replaces both services
**PostgreSQL** - Concurrent write safety at 500-user scale
**Discord `/link` slash command** - Mojang API validation
**Pterodactyl File Management API** - Replaces WebSocket console
**Hybrid sync** - Event-driven push + hourly cron reconciliation
**UUIDs with dashes** - Minecraft 1.8+ requirement
---
## Implementation Phases
### Phase 1: Database Migration
- [ ] Provision PostgreSQL 15+
- [ ] Execute schema creation
- [ ] Apply indexes for 500-user scale
- [ ] Migrate legacy SQLite data
### Phase 2: Core Functions
- [ ] pg connection pool
- [ ] Mojang API validation + UUID formatting
- [ ] Pterodactyl Application API (auto-discovery)
- [ ] Pterodactyl Client API (file write, commands)
### Phase 3: Discord Integration
- [ ] `/link` slash command registration
- [ ] UUID validation flow
- [ ] Auto-DM new subscribers
- [ ] Paymenter webhook updates
### Phase 4: Sync System
- [ ] Event-driven immediate sync
- [ ] node-cron hourly reconciliation
- [ ] Sequential batch processing
- [ ] Sync logging to database
### Phase 5: Admin Panel
- [ ] server_sync_log dashboard
- [ ] Manual "Force Sync" trigger
- [ ] Linked accounts view
- [ ] Production deployment
---
## Critical Gotchas
From Gemini consultation - these will break production if missed:
1. **Content-Type: text/plain** for Panel file write (NOT application/json)
2. **Mojang UUIDs without dashes**, Minecraft needs **WITH dashes**
3. **HTTP 412 = server offline**, NOT an error (file saved for next boot)
4. **Sequential processing** prevents Panel API rate limiting
5. **Application API** for discovery, **Client API** for file operations
---
## Database Schema
```sql
CREATE TABLE users (
discord_id VARCHAR(255) PRIMARY KEY,
minecraft_username VARCHAR(255) UNIQUE,
minecraft_uuid VARCHAR(255) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE subscriptions (
id SERIAL PRIMARY KEY,
discord_id VARCHAR(255) REFERENCES users(discord_id),
tier_level INT NOT NULL,
status VARCHAR(50) NOT NULL, -- active, grace_period, cancelled
stripe_customer_id VARCHAR(255)
);
CREATE TABLE server_sync_log (
server_identifier VARCHAR(50) PRIMARY KEY,
last_successful_sync TIMESTAMP,
last_error TEXT,
is_online BOOLEAN DEFAULT true
);
CREATE INDEX idx_users_discord_id ON users(discord_id);
CREATE INDEX idx_subscriptions_status ON subscriptions(status);
```
---
## Success Criteria
- User subscribes → links account → auto-whitelisted within 5 minutes
- Master whitelist = database (single source of truth)
- All servers sync from master (hourly reconciliation)
- Server discovery is automatic (no hardcoded names)
- Works reliably for remote RV operations
- Handles 500 subscribers by September 2027
---
## What Gets Retired
Once Arbiter 2.x is deployed:
- ❌ Old Whitelist Manager (Python Flask on Billing VPS)
- ❌ Old Arbiter 1.x (if not already migrated)
- ✅ Update all documentation to reference unified system
---
## Dependencies
- PostgreSQL 15+
- Node.js 20 LTS
- Pterodactyl Panel v1.12.1 (Client + Application API keys)
- Mojang API (public, no auth)
- Discord Bot with slash command permissions
---
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️