Files
firefrost-operations-manual/docs/planning/dynamic-servers-page-implementation.md
Claude (Chronicler #56) f5071a65c9 docs: update Servers page security policy - IPs Discord-gated
WHAT WAS DONE:
Updated dynamic Servers page implementation plan to reflect new security
policy: server IP addresses and connection details will NOT be displayed
on public website. Access gated behind Discord with minimum Awakened tier
verification.

CHANGES MADE:
- Added security policy section to Executive Summary
- Removed 'Copy IP Button' from Easy Wins section
- Updated frontend features to remove IP display
- Added 'Join via Discord' CTA messaging
- Updated test checklist to verify NO IPs visible
- Added IP Address Protection section to Security Considerations
- Emphasized community engagement and FOMO strategy

RATIONALE:
- Prevents random server scanning and bot attacks
- Drives Discord engagement (community FOMO)
- Encourages subscription conversion (Awakened minimum)
- Maintains public status display (Online/Offline, player counts)
- Worker still fetches IP data, frontend just doesn't render it

This is a smart security decision that also serves the business model.

File: docs/planning/dynamic-servers-page-implementation.md
Lines changed: ~30

Signed-off-by: Claude (Chronicler #56) <claude@firefrostgaming.com>
2026-04-03 04:43:20 +00:00

18 KiB

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

# 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

npm install -g wrangler

Task 2.2: Create Worker Project

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:

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

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

wrangler deploy

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

Task 3.2: Configure Production Secrets

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

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:
setInterval(() => {
  fetch(WORKER_URL).then(/* update UI */);
}, 60000); // Every 60 seconds
  1. Discord CTA:
<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:

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:

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:

'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:

Pterodactyl Resources:

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)