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>
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
/resourcesendpoint 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
- Log into Pterodactyl Admin panel (https://panel.firefrostgaming.com)
- Go to Users → Create new user:
- Username:
website-api - Email:
website-api@firefrostgaming.com - Password: (generate secure password)
- Username:
- 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!)
- Log out of Admin account
- Log in as
website-api@firefrostgaming.com - Go to Account Settings → API Credentials
- Create new Client API key
- 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/clientendpoint - Gets live server list
- For each server, fetches
/resourcesendpoint 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
- Auto-Refresh:
setInterval(() => {
fetch(WORKER_URL).then(/* update UI */);
}, 60000); // Every 60 seconds
- 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
- Commit changes to git
- Push to Gitea (auto-deploys to GitHub → Cloudflare Pages)
- Wait 60-90 seconds
- Test
https://firefrost-website.pages.dev/servers - 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
- Log into Uptime Kuma (Command Center)
- 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
- 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
- Go to Cloudflare Dashboard → Workers → servers-api
- View Analytics tab
- 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:
- Log into Cloudflare Dashboard
- Go to Workers & Pages → firefrost-website
- Settings → Domains → Add custom domain
- Enter:
firefrostgaming.com - Cloudflare provisions SSL certificate (1-5 minutes)
- Wait for edge network propagation (5-15 minutes)
- Test: Visit
https://firefrostgaming.com - Verify all pages load correctly
- 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):
- Remove custom domain from Cloudflare Pages
- Old Ghost site becomes active again
- Debug issue
- Try cutover again when ready
Player Count Nuance (Important!)
From Gemini:
The Client API does return a
playersinteger underattributes.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:
- MVP (Launch): Show player counts IF Pterodactyl provides them
- Monitor: Watch for false zeros (server shows "0 players" but you know people are online)
- If unreliable: Remove player count display post-launch
- 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 thiss-maxage=60: Cloudflare Edge caches for 60 secondsmax-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!)
- Auto-Refresh -
setIntervalevery 60 seconds - Status Pulse Animation - CSS keyframe already provided
- "Join via Discord" CTA - Clear messaging directing users to Discord for server IPs
⚠️ Possible But Complex
- Modpack logos - Would need image hosting, careful sizing
- Server tags - Requires Pterodactyl custom fields
- "Last seen online" - Requires tracking state changes
❌ Architectural Nightmares (Skip!)
- Historical uptime tracking - Needs database (kills static site)
- Live console output - Security risk + WebSocket complexity
- 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.varsfile 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.comorigin - ✅ 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:
- Zero VPS dependencies: Worker is serverless, no SSH needed
- Automatic recovery: If panel goes down temporarily, Worker error handling keeps site usable
- Push notifications: Discord webhooks alert Michael/Meg immediately
- Edge monitoring: Uptime Kuma on Command Center watches Worker health
- 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:
- Create it in Pterodactyl
- Add Service Account as subuser
- Set description to
Modpack Name | vX.Y.Z - 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)