# Dynamic Servers Page Implementation Plan

**Created:** April 3, 2026  
**Status:** Ready for Implementation  
**Target Launch:** April 15, 2026 (12 days)  
**Architecture:** Cloudflare Workers + Pterodactyl Client API

---

## Executive Summary

**The Problem:**
The Servers page is currently static HTML. Every time we add/remove/modify a Minecraft server, we must manually edit website code and redeploy. This is unsustainable for a hosting company.

**The Solution:**
Cloudflare Workers acting as a serverless proxy between the website and Pterodactyl Panel. The Worker fetches live server status from Pterodactyl's Client API, caches at the edge for 60 seconds, and serves JSON to the frontend. Zero manual updates required.

**Why This Architecture:**
- ✅ **Serverless:** No VPS to maintain (critical for RV travel!)
- ✅ **Secure:** API keys hidden in Worker environment variables, IPs gated behind Discord
- ✅ **Fast:** Edge caching protects Pterodactyl from traffic spikes
- ✅ **Free:** Cloudflare Workers free tier covers our needs
- ✅ **RV-Ready:** Fully decoupled, graceful degradation if panel goes down

**Security Policy:**
Server IP addresses and connection details are **NOT publicly displayed** on the website. They are gated behind Discord access with minimum Awakened tier ($1/month) verification. This drives community engagement and prevents random server scanning.

---

## Architecture Overview

```
User Browser
    ↓
firefrostgaming.com/servers (11ty static page)
    ↓ (JavaScript fetch)
Cloudflare Worker (https://servers-api.YOUR-WORKER.workers.dev)
    ↓ (Server-side API call, API key hidden)
Pterodactyl Client API (/api/client/servers)
    ↓ (Returns server list + live status)
Worker (caches for 60 seconds at edge)
    ↓
JSON response → Browser renders
```

**Key Insight from Gemini:**
If 1,000 users hit the site simultaneously, the FIRST user triggers the Worker → Pterodactyl API call (400ms). The remaining 999 users get cached data from Cloudflare's edge in ~10ms. Pterodactyl only gets hit once per minute regardless of traffic!

---

## Critical Pterodactyl API Distinction

### Two APIs, Two Purposes

**Application API (Admin) - `ptla_` prefix:**
- Used for administrative tasks
- Lists ALL servers regardless of owner
- **Cannot provide real-time player counts or live stats**
- Good for: Trinity Panel monitoring, server creation/deletion

**Client API (User-Facing) - `ptlc_` prefix:**
- Used for end-user access
- Only shows servers the user has access to
- **Provides `/resources` endpoint with live stats**
- Good for: Website status display, player counts

**We need the Client API for the website!**

---

## Implementation Plan (12 Days)

### Phase 1: Pterodactyl Setup (April 3-4)

**Task 1.1: Check Existing API Key Type**
```bash
# Look at Trinity Panel API key
# If it starts with ptla_ = Application API (wrong for this!)
# If it starts with ptlc_ = Client API (perfect!)
```

**Task 1.2: Create Service Account**
1. Log into Pterodactyl Admin panel (https://panel.firefrostgaming.com)
2. Go to **Users** → Create new user:
   - Username: `website-api`
   - Email: `website-api@firefrostgaming.com`
   - Password: (generate secure password)
3. For EACH Minecraft server we want public:
   - Go to **Servers** in Admin panel
   - Click the server
   - Go to **Subusers** tab
   - Add `website-api@firefrostgaming.com`
   - Grant ONLY these permissions:
     * ✅ View Server
     * ✅ View Server Statistics
     * ❌ Everything else (no control permissions!)
4. Log out of Admin account
5. Log in as `website-api@firefrostgaming.com`
6. Go to Account Settings → **API Credentials**
7. Create new Client API key
8. **SAVE THIS KEY!** It starts with `ptlc_`

**Task 1.3: Standardize Server Descriptions**
Update ALL Minecraft server descriptions in Pterodactyl to follow this format:
```
All The Mods 10 | v1.0.9
Stoneblock 3 | v1.2.4
Project Architect 2 | v2.0.1
Society | v1.1.0
Mythcraft | v2.3.1
All The Mons | v1.0.5
```

**Format:** `Modpack Name | v[Version]`

**Why pipe-delimited:**
- Clean, readable in Pterodactyl UI
- Easy JavaScript parsing: `const [name, version] = desc.split(' | ');`
- No special characters that break JSON

---

### Phase 2: Local Development (April 4-6)

**Task 2.1: Install Wrangler CLI**
```bash
npm install -g wrangler
```

**Task 2.2: Create Worker Project**
```bash
mkdir cloudflare-workers
cd cloudflare-workers
mkdir servers-api
cd servers-api
wrangler init
```

**Task 2.3: Create .dev.vars (Local Secrets)**
Create file: `.dev.vars` in worker directory:
```env
PANEL_URL="https://panel.firefrostgaming.com"
CLIENT_API_KEY="ptlc_YOUR_ACTUAL_KEY_HERE"
```

**IMPORTANT:** Add `.dev.vars` to `.gitignore` - NEVER commit secrets!

**Task 2.4: Write Worker Code**
See `cloudflare-worker-code.js` in this directory for complete code.

Key features:
- Fetches from Pterodactyl Client API `/api/client` endpoint
- Gets live server list
- For each server, fetches `/resources` endpoint for status
- Returns JSON with server name, status, players, description
- Handles CORS for firefrostgaming.com
- Edge caching: 60 seconds
- Graceful error handling if panel is down

**Task 2.5: Test Locally**
```bash
wrangler dev
# Worker runs at http://localhost:8787
# Open browser, test endpoint
# Verify JSON structure matches expectations
```

**Test cases:**
- ✅ All servers listed
- ✅ Online/Offline status correct
- ✅ Player counts (if available)
- ✅ Modpack names parsed correctly
- ✅ CORS headers present
- ✅ Error handling works (simulate panel down)

---

### Phase 3: Deploy to Production (April 7-9)

**Task 3.1: Deploy Worker**
```bash
wrangler deploy
```

**Output will be:** `https://servers-api.YOUR-WORKER.workers.dev`

**Task 3.2: Configure Production Secrets**
```bash
wrangler secret put PANEL_URL
# Enter: https://panel.firefrostgaming.com

wrangler secret put CLIENT_API_KEY
# Paste the ptlc_ key
```

**Task 3.3: Test Production Worker**
```bash
curl https://servers-api.YOUR-WORKER.workers.dev
# Should return JSON with all servers
```

**Task 3.4: Update Cloudflare Worker Settings**
- Go to Cloudflare Dashboard → Workers
- Click `servers-api`
- Settings → Triggers → Add custom domain (optional): `api.firefrostgaming.com`
- Verify CORS settings

---

### Phase 4: Frontend Integration (April 9-11)

**Task 4.1: Update servers.njk**
See `servers-page-frontend.html` in this directory for complete code.

Key features:
- Static HTML container
- JavaScript fetches from Worker on page load
- Renders server cards dynamically
- "Pulse" animation for online status (Frost cyan)
- Offline status in Fire orange
- Player count display (if available)
- **NO IP addresses displayed** (Discord-gated security policy)
- "Join via Discord" call-to-action messaging
- Error handling with friendly message
- Auto-refresh every 60 seconds

**Task 4.2: Add Easy Wins**
1. **Auto-Refresh:**
```javascript
setInterval(() => {
  fetch(WORKER_URL).then(/* update UI */);
}, 60000); // Every 60 seconds
```

2. **Discord CTA:**
```html
<div class="join-instructions">
  <p>Server IPs available in Discord for Awakened+ members!</p>
  <a href="/discord" class="discord-button">Join Discord →</a>
</div>
```

**Task 4.3: Test on Preview**
1. Commit changes to git
2. Push to Gitea (auto-deploys to GitHub → Cloudflare Pages)
3. Wait 60-90 seconds
4. Test `https://firefrost-website.pages.dev/servers`
5. Verify on multiple devices:
   - Desktop (Firefox, Chrome)
   - Mobile (Michael's phone, Meg's phone)
   - Tablet

**Test checklist:**
- ✅ All servers display
- ✅ Status indicators correct (green pulse = online, red = offline)
- ✅ Player counts showing (if Pterodactyl provides them)
- ✅ "Join via Discord" messaging clear and prominent
- ✅ Auto-refresh updates without page reload
- ✅ Mobile responsive (no horizontal scroll!)
- ✅ Error message displays if Worker fails
- ✅ Loading state shows during fetch
- ✅ NO IP addresses or ports visible on public page

---

### Phase 5: Monitoring Setup (April 11-12)

**Task 5.1: Uptime Kuma Monitoring**
1. Log into Uptime Kuma (Command Center)
2. Add new monitor:
   - Name: "Servers API Worker"
   - Type: HTTP(s)
   - URL: `https://servers-api.YOUR-WORKER.workers.dev`
   - Interval: 120 seconds (2 minutes)
   - Retries: 3
3. Set up Discord notification:
   - Webhook URL: (Trinity Discord channel)
   - Alert on: Down, Error, Recovery

**Task 5.2: Worker Error Webhook (Optional)**
Add Discord webhook to Worker error handler:
```javascript
catch (error) {
  // Alert Discord that Worker failed
  fetch('https://discord.com/api/webhooks/YOUR_WEBHOOK', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      content: '🚨 Servers API Worker failed to reach Pterodactyl Panel!'
    })
  });
  
  // Return graceful error to users
  return new Response(JSON.stringify({
    error: "Servers temporarily unreachable",
    servers: []
  }), { status: 500 });
}
```

**Task 5.3: Cloudflare Analytics**
1. Go to Cloudflare Dashboard → Workers → servers-api
2. View Analytics tab
3. Monitor:
   - Request count
   - Error rate
   - CPU time usage
   - P95 response time

**Target metrics:**
- Error rate: <1%
- Response time: <500ms
- CPU time: <5ms (network time doesn't count)

---

### Phase 6: DNS Cutover (April 14)

**CRITICAL: Do NOT attempt DNS cutover until Servers page tested thoroughly on preview!**

**Pre-Flight Checklist:**
- ✅ Cloudflare Worker deployed and tested
- ✅ servers.njk updated and tested on preview
- ✅ All 7 pages working on firefrost-website.pages.dev
- ✅ Mobile responsive verified
- ✅ Uptime Kuma monitoring active
- ✅ Discord alerts tested
- ✅ Michael + Meg + Holly have reviewed site
- ✅ Paymenter subscription links verified
- ✅ /discord redirect working

**DNS Cutover Steps:**
1. Log into Cloudflare Dashboard
2. Go to Workers & Pages → firefrost-website
3. Settings → Domains → Add custom domain
4. Enter: `firefrostgaming.com`
5. Cloudflare provisions SSL certificate (1-5 minutes)
6. Wait for edge network propagation (5-15 minutes)
7. Test: Visit `https://firefrostgaming.com`
8. Verify all pages load correctly
9. Check Servers page specifically

**Expected "routing weirdness" (Gemini's warning):**
- First 5-15 minutes: Some users may see old Ghost site, some see new 11ty site
- After 15 minutes: All users see new site
- This is normal edge network propagation

**Rollback Plan (if needed):**
1. Remove custom domain from Cloudflare Pages
2. Old Ghost site becomes active again
3. Debug issue
4. Try cutover again when ready

---

## Player Count Nuance (Important!)

**From Gemini:**
> The Client API does return a `players` integer under `attributes.resources.players`. However, Pterodactyl's Wings daemon gets this number by passively querying the Minecraft server. For heavily modded servers (like ATM10), the server query port sometimes hangs or reports 0 even when players are online.

**Strategy:**
1. **MVP (Launch):** Show player counts IF Pterodactyl provides them
2. **Monitor:** Watch for false zeros (server shows "0 players" but you know people are online)
3. **If unreliable:** Remove player count display post-launch
4. **DO NOT:** Try to build custom RCON/Query proxy 12 days before launch

**Keep it simple:** Online/Offline status is the MVP. Player counts are a nice-to-have.

---

## Server Naming Convention (Enforced Standard)

**Format:** `Modpack Name | v[Version]`

**Examples:**
```
All The Mods 10 | v1.0.9
Stoneblock 3 | v1.2.4
Project Architect 2 | v2.0.1
Society | v1.1.0
Mythcraft | v2.3.1
All The Mons | v1.0.5
```

**JavaScript parsing:**
```javascript
const description = "All The Mods 10 | v1.0.9";
const [modpackName, version] = description.split(' | ');
// modpackName = "All The Mods 10"
// version = "v1.0.9"
```

**Where to store:** Pterodactyl Server Description field (visible in Client API)

**Maintenance:** When adding new servers, Michael MUST follow this format for automatic website updates!

---

## Caching Strategy

**Cache-Control Header:**
```javascript
'Cache-Control': 'public, s-maxage=60, max-age=60'
```

**What this means:**
- `public`: Anyone can cache this
- `s-maxage=60`: Cloudflare Edge caches for 60 seconds
- `max-age=60`: User's browser caches for 60 seconds

**Traffic pattern example:**
```
Time 00:00 - User A visits → Worker executes → Pterodactyl queried → Result cached
Time 00:15 - User B visits → Cloudflare Edge returns cached data (no Worker execution!)
Time 00:30 - User C visits → Cloudflare Edge returns cached data (no Worker execution!)
Time 00:45 - User D visits → Cloudflare Edge returns cached data (no Worker execution!)
Time 01:01 - User E visits → Cache expired → Worker executes → Pterodactyl queried → New cache
```

**Result:** Pterodactyl only gets hit ONCE per minute, regardless of website traffic!

**Why 60 seconds is acceptable:**
Minecraft server status doesn't change rapidly. If a server goes down, users will see the old "Online" status for up to 60 seconds max. This is totally fine for this use case.

---

## Future Enhancements (Post-Launch)

### ✅ Easy Wins (Add These!)
1. **Auto-Refresh** - `setInterval` every 60 seconds
2. **Status Pulse Animation** - CSS keyframe already provided
3. **"Join via Discord" CTA** - Clear messaging directing users to Discord for server IPs

### ⚠️ Possible But Complex
1. **Modpack logos** - Would need image hosting, careful sizing
2. **Server tags** - Requires Pterodactyl custom fields
3. **"Last seen online"** - Requires tracking state changes

### ❌ Architectural Nightmares (Skip!)
1. **Historical uptime tracking** - Needs database (kills static site)
2. **Live console output** - Security risk + WebSocket complexity
3. **Custom RCON integration** - Over-engineering for MVP

**Philosophy:** Ship the MVP. Add features iteratively after launch based on actual user feedback.

---

## Security Considerations

### API Key Protection
- ✅ Client API key stored in Cloudflare Worker environment variables
- ✅ Never exposed to browser
- ✅ `.dev.vars` file in `.gitignore`
- ✅ Service Account has minimal permissions (read-only)

### IP Address Protection (Community Engagement Strategy)
- ✅ Server IPs and ports **NOT displayed** on public website
- ✅ Connection details gated behind Discord access
- ✅ Minimum Awakened tier ($1/month) required
- ✅ Drives community engagement and FOMO
- ✅ Prevents random server scanning and bot attacks
- ✅ Worker still fetches IP data but frontend doesn't render it

### CORS Configuration
- ✅ Only allows `https://firefrostgaming.com` origin
- ✅ Rejects requests from other domains
- ✅ Handles OPTIONS preflight requests

### Rate Limiting
- ✅ Edge caching prevents API abuse
- ✅ Pterodactyl only hit once per minute max
- ✅ No user can DOS the panel through the website

### Error Disclosure
- ✅ Generic error messages to users ("Servers temporarily unreachable")
- ❌ Never expose API endpoints or keys in error messages
- ✅ Detailed errors logged to Cloudflare Analytics only

---

## RV Travel Considerations

**Why this architecture works for remote operation:**

1. **Zero VPS dependencies:** Worker is serverless, no SSH needed
2. **Automatic recovery:** If panel goes down temporarily, Worker error handling keeps site usable
3. **Push notifications:** Discord webhooks alert Michael/Meg immediately
4. **Edge monitoring:** Uptime Kuma on Command Center watches Worker health
5. **Simple troubleshooting:** Only 3 moving parts (Worker, Pterodactyl, Frontend)

**Troubleshooting from the road:**
- Panel down? Uptime Kuma + Discord alert
- Worker failing? Cloudflare Dashboard shows errors
- Website broken? Cloudflare Pages deployment logs
- All accessible via phone/tablet over cellular!

---

## Timeline & Milestones

**April 3 (Today):**
- ✅ Check existing API key type
- ✅ Create Service Account in Pterodactyl
- ✅ Standardize server descriptions

**April 4-6 (Weekend):**
- ✅ Install Wrangler CLI
- ✅ Build Worker locally
- ✅ Test with real Pterodactyl API

**April 7-9:**
- ✅ Deploy Worker to production
- ✅ Configure secrets
- ✅ Test Worker endpoint

**April 9-11:**
- ✅ Update servers.njk frontend
- ✅ Add Copy IP + Auto-refresh features
- ✅ Test on preview extensively

**April 11-12:**
- ✅ Set up Uptime Kuma monitoring
- ✅ Configure Discord alerts
- ✅ Final QA testing

**April 14:**
- ✅ Pre-flight checklist verification
- ✅ DNS cutover to firefrostgaming.com
- 🎉 **SOFT LAUNCH!**

**April 15:**
- Buffer day for unexpected issues
- Monitor analytics closely
- Celebrate! 🔥⚡❄️

---

## Success Metrics (Post-Launch)

**Week 1 (April 15-21):**
- Worker uptime: >99%
- Page load time: <2 seconds
- Error rate: <1%
- Zero manual server list updates needed

**Month 1 (April-May):**
- Player count accuracy verified
- Auto-refresh working smoothly
- Mobile traffic analytics reviewed
- User feedback collected (Discord)

**Long-term:**
- Add/remove servers without touching website code
- Update modpack versions by changing Pterodactyl description only
- Infrastructure "just works" from anywhere in the USA

---

## Contacts & Resources

**Gemini AI Consultation:**
- Session 1: Initial architecture recommendation (Cloudflare Workers)
- Session 2: Implementation details and Q&A
- Both consultations saved in `docs/planning/gemini-servers-consultation/`

**Cloudflare Resources:**
- Workers Dashboard: https://dash.cloudflare.com/workers
- Wrangler Docs: https://developers.cloudflare.com/workers/wrangler/
- Workers Examples: https://workers.cloudflare.com/examples

**Pterodactyl Resources:**
- API Documentation: https://dashflo.net/docs/api/pterodactyl/v1/
- Client API Endpoints: /api/client, /api/client/servers/{id}/resources
- Panel URL: https://panel.firefrostgaming.com

**Team:**
- Michael "Frostystyle" - Technical implementation
- Claude (Chronicler #56) - Documentation and pair programming
- Gemini AI - Architectural guidance and code review
- Butter No Nutters - CEO approval (royal seal granted! 😺👑)

---

## Conclusion

This implementation transforms the Servers page from a static, manually-maintained list into a **living, self-updating system** that reflects reality automatically. When we add a new Minecraft server, we:

1. Create it in Pterodactyl
2. Add Service Account as subuser
3. Set description to `Modpack Name | vX.Y.Z`
4. **DONE!** Website updates automatically within 60 seconds.

Zero code changes. Zero deployments. Zero manual updates.

**That's the vision:** Build it once, run it forever.

**Fire + Frost + Foundation = Where Love Builds Legacy** 💙

---

**Document Status:** Complete and ready for implementation  
**Last Updated:** April 3, 2026  
**Next Review:** April 15, 2026 (post-launch retrospective)
