Files
firefrost-operations-manual/docs/planning/gemini-servers-consultation/03-gemini-initial-response.md
Claude (Chronicler #56) 7c638ee68a docs: complete dynamic Servers page implementation plan
WHAT WAS DONE:
Created comprehensive implementation plan for dynamic Servers page using
Cloudflare Workers + Pterodactyl Client API. Includes complete Gemini AI
consultation archive with architectural recommendations.

NEW DOCUMENTATION:
1. docs/planning/dynamic-servers-page-implementation.md (737 lines)
   - Executive summary and architecture overview
   - 12-day implementation timeline (April 3-15, 2026)
   - Phase-by-phase task breakdown
   - Security considerations and RV travel requirements
   - Caching strategy and performance metrics
   - Success criteria and monitoring setup

2. docs/planning/gemini-servers-consultation/
   - 01-initial-consultation.md (original prompt to Gemini)
   - 02-followup-questions.md (7 technical clarification questions)
   - 03-gemini-initial-response.md (Cloudflare Workers architecture)
   - 04-gemini-followup-response.md (implementation Q&A)

KEY ARCHITECTURAL DECISIONS:
-  Cloudflare Workers (serverless, zero maintenance, RV-ready)
-  Pterodactyl Client API (not Application API!)
-  Service Account pattern (read-only permissions)
-  Edge caching (60 seconds, protects Pterodactyl from traffic spikes)
-  Pipe-delimited naming: 'Modpack Name | vX.Y.Z'

IMPLEMENTATION PHASES:
Phase 1 (Apr 3-4): Pterodactyl Service Account setup
Phase 2 (Apr 4-6): Local Worker development with Wrangler
Phase 3 (Apr 7-9): Deploy Worker to production
Phase 4 (Apr 9-11): Frontend integration + testing
Phase 5 (Apr 11-12): Uptime Kuma monitoring setup
Phase 6 (Apr 14): DNS cutover to firefrostgaming.com

EASY WINS TO ADD:
- Copy Server IP button (clipboard.writeText)
- Auto-refresh every 60 seconds (setInterval)
- Pulse animation for online status (CSS provided)

SKIP (TOO COMPLEX):
- Historical uptime tracking (requires database)
- Live console output (security risk + WebSocket complexity)

GEMINI VALIDATION:
'Your timeline is highly realistic. Get that Service Account created
today, mock up the .dev.vars this weekend, and you'll be coasting into
April 15.'

Butter No Nutters (CEO) has granted royal approval on architecture. 😺👑

This is the final blocker before soft launch. Once implemented, Servers
page will auto-update when infrastructure changes - zero manual edits
required.

Fire + Frost + Foundation = Where Love Builds Legacy 💙

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

8.4 KiB

Gemini Response: Initial Architecture Recommendation

Date: April 3, 2026
Topic: Dynamic Servers Page Architecture
Consultant: Gemini AI


Michael, Claude, and the rest of the Trinity—first of all, congratulations on that 45-minute migration. That is the definition of executing with precision.

To answer your core question immediately: Your "Aha!" moment was completely right. Cloudflare Workers is the undisputed champion for this architecture. Option 2 (VPS Proxy) introduces unnecessary infrastructure debt. As a team planning to run operations from an RV over cellular networks, every piece of server infrastructure you add is another potential point of failure you have to troubleshoot from the road.

A Cloudflare Worker is serverless, requires zero maintenance, hides your API keys, runs on the same edge network as your website, and will cost you absolutely nothing on the free tier.

Here is your complete architectural blueprint for making the Servers page dynamic before your April 15 soft launch.


🚨 The Pterodactyl API "Gotcha" (Crucial Architecture Insight)

Before we write code, we need to address how Pterodactyl's API actually works, because it trips up a lot of developers.

Pterodactyl has two APIs:

  1. Application API (Admin): Good for getting server names, IDs, and allocation details. Bad because it cannot give you real-time player counts or live CPU/RAM usage.
  2. Client API (User-Facing): This is the one you need. It provides the /resources endpoint, which returns current_state (running/offline) and real-time player counts.

The Strategy: The "Service Account" Pattern To use the Client API securely without exposing admin privileges:

  1. Create a dummy user in your Pterodactyl panel (e.g., api-service@...).
  2. Add this user as a Subuser to only the Minecraft servers you want public on the website. Give it strictly "Read" permissions (View Server, View Stats).
  3. Generate a Client API Key for this dummy account. Note: I will use generic URLs in the examples below to keep your specific infrastructure secure.

🛠️ Step 1: The Cloudflare Worker Proxy

Cloudflare Workers give you 10ms of CPU time on the free tier, but network requests (waiting for the API) don't count toward that limit. You can easily fetch the data and aggregate it.

Create a new Worker in your Cloudflare dashboard and use this code:

export default {
  async fetch(request, env) {
    // 1. Handle CORS preflight requests
    if (request.method === 'OPTIONS') {
      return new Response(null, {
        headers: {
          'Access-Control-Allow-Origin': 'https://your-website-domain.com', // Update this!
          'Access-Control-Allow-Methods': 'GET, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type, Authorization'
        }
      });
    }

    // Set these in Cloudflare Worker Environment Variables!
    const PANEL_URL = env.PANEL_URL; // e.g., https://panel.example.com
    const API_KEY = env.CLIENT_API_KEY;

    try {
      // 2. Fetch the list of servers the Service Account has access to
      const listResponse = await fetch(`${PANEL_URL}/api/client`, {
        headers: {
          'Authorization': `Bearer ${API_KEY}`,
          'Accept': 'application/json'
        }
      });
      
      const listData = await listResponse.json();
      
      if (!listData.data) throw new Error("Failed to fetch server list");

      // 3. Fetch live stats for ALL servers concurrently
      const serverPromises = listData.data.map(async (server) => {
        const id = server.attributes.identifier;
        
        // Fetch live resources (players, status)
        const statsResponse = await fetch(`${PANEL_URL}/api/client/servers/${id}/resources`, {
           headers: {
            'Authorization': `Bearer ${API_KEY}`,
            'Accept': 'application/json'
          }
        });
        
        const stats = await statsResponse.json();
        const isRunning = stats.attributes?.current_state === 'running';

        return {
          id: id,
          name: server.attributes.name,
          node: server.attributes.node,
          status: isRunning ? 'Online' : 'Offline',
          players: isRunning ? stats.attributes?.resources?.uptime ? `${stats.attributes.resources.players} Online` : '0 Online' : '-',
          // You can parse modpack/version from the server description if you enforce a naming convention!
          description: server.attributes.description
        };
      });

      const finalServers = await Promise.all(serverPromises);

      // 4. Return the aggregated data with Edge Caching
      return new Response(JSON.stringify({ servers: finalServers }), {
        headers: {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': 'https://your-website-domain.com', // Update this!
          'Cache-Control': 'max-age=60' // CRITICAL: Caches at the Edge for 60 seconds
        }
      });

    } catch (error) {
      // RV-Resiliency: If the panel is down, fail gracefully.
      return new Response(JSON.stringify({ error: "Servers temporarily unreachable", servers: [] }), {
        status: 500,
        headers: { 'Access-Control-Allow-Origin': '*' }
      });
    }
  }
}

Why this is bulletproof:

  • Cache-Control: max-age=60: Even if 1,000 people load your Servers page at the exact same second, Cloudflare intercepts it. Your panel only gets hit once per minute. It completely protects your infrastructure from traffic spikes.
  • Security: The CLIENT_API_KEY lives securely in Cloudflare's environment variables. The browser never sees it.

🖥️ Step 2: The 11ty Frontend Integration

On your servers.njk page, you'll leave a static HTML container and use vanilla JavaScript to populate it.

Keep your visual design, but add a pulsing dot for the online status.

<div class="ffg-container">
  <h2>Live Infrastructure</h2>
  <div id="server-grid" class="grid-layout">
    <p class="loading-text">Establishing connection to the grid...</p>
  </div>
</div>

<script>
  document.addEventListener("DOMContentLoaded", () => {
    const grid = document.getElementById('server-grid');
    // Replace with your actual Cloudflare Worker URL
    const WORKER_URL = 'https://servers-api.your-worker.workers.dev'; 

    fetch(WORKER_URL)
      .then(res => res.json())
      .then(data => {
        if (data.error || !data.servers.length) {
          grid.innerHTML = `<p class="error-text">Navigation systems offline. Our engineers are investigating.</p>`;
          return;
        }

        grid.innerHTML = data.servers.map(server => `
          <div class="server-card">
            <h3>${server.name}</h3>
            <p class="desc">${server.description || 'Standard Minecraft Deployment'}</p>
            <div class="status-row">
              <span class="status-badge ${server.status.toLowerCase()}">
                ${server.status === 'Online' ? '<span class="pulse-dot"></span>' : ''} ${server.status}
              </span>
              <span class="player-count">${server.players}</span>
            </div>
          </div>
        `).join('');
      })
      .catch(err => {
        grid.innerHTML = `<p class="error-text">Unable to establish telemetry with the fleet.</p>`;
      });
  });
</script>

<style>
  /* Quick CSS for the "Pulse" animation you wanted */
  .pulse-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    background-color: #4ecdc4; /* Frost brand color */
    border-radius: 50%;
    margin-right: 6px;
    animation: pulse 2s infinite;
  }
  
  @keyframes pulse {
    0% { box-shadow: 0 0 0 0 rgba(78, 205, 196, 0.7); }
    70% { box-shadow: 0 0 0 6px rgba(78, 205, 196, 0); }
    100% { box-shadow: 0 0 0 0 rgba(78, 205, 196, 0); }
  }
  
  .status-badge.offline { color: #FF6B35; /* Fire brand color */ }
</style>

🗺️ The RV Reality Check

This architecture perfectly supports the travel vision.

  • If a server crashes, the Worker automatically detects it on the next 60-second polling cycle and updates the website to "Offline" without you lifting a finger.
  • If the entire panel goes offline, the catch block in the frontend JS elegantly displays an error message. The rest of your website remains lightning fast and perfectly usable because it is hosted on Cloudflare's edge, entirely decoupled from your backend infrastructure.

You have 12 days. Set up the Service Account in Pterodactyl, drop the Worker code into Cloudflare, point your frontend at it, and you are ready for the DNS cutover.