<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://apptolast.github.io/FicsitDocumentation/feed.xml" rel="self" type="application/atom+xml" /><link href="https://apptolast.github.io/FicsitDocumentation/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-04-16T02:21:57+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/feed.xml</id><title type="html">FICSIT.monitor Docs</title><subtitle>Official documentation for FICSIT.monitor — the real-time Satisfactory server monitoring platform. Learn how to set up your dedicated server, install FRM, and connect to the dashboard.</subtitle><entry><title type="html">Server Won’t Connect: Diagnosing Connection Issues</title><link href="https://apptolast.github.io/FicsitDocumentation/posts/connection-errors/" rel="alternate" type="text/html" title="Server Won’t Connect: Diagnosing Connection Issues" /><published>2026-04-15T02:27:00+02:00</published><updated>2026-04-15T02:27:00+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/posts/connection-errors</id><content type="html" xml:base="https://apptolast.github.io/FicsitDocumentation/posts/connection-errors/"><![CDATA[<h2 id="overview">Overview</h2>

<p>This guide provides a systematic diagnostic procedure for connection failures between
FICSIT.monitor and your Satisfactory server. Work through the steps in order to
isolate the root cause.</p>

<hr />

<h2 id="quick-diagnostic-flowchart">Quick Diagnostic Flowchart</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre>Is the server container running?
  └── No → docker start satisfactory-server, wait 2 min
  └── Yes ↓

Does HealthCheck respond?
  └── No → Port 7777 is blocked or server crashed → Step 1
  └── Yes ↓

Does PasswordLogin succeed?
  └── No (wrong_password) → Admin password is wrong → Step 2
  └── Yes ↓

Does FRM respond?
  └── No → Port 8080 blocked or FRM not installed → Step 3
  └── Yes ↓

All checks pass → contact support
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="step-1-verify-server-is-running-and-port-7777-is-open">Step 1: Verify Server is Running and Port 7777 is Open</h2>

<h3 id="check-docker-container">Check Docker container</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>docker ps | <span class="nb">grep </span>satisfactory
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Expected: shows <code class="language-plaintext highlighter-rouge">Up X hours</code> or <code class="language-plaintext highlighter-rouge">Up X minutes</code>.</p>

<p>If not running:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>docker start satisfactory-server
docker logs <span class="nt">-f</span> satisfactory-server | <span class="nb">grep</span> <span class="s2">"InProgress"</span>
<span class="c"># Wait for "LogGameState: Match State Changed from WaitingToStart to InProgress"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="check-port-7777-is-listening">Check port 7777 is listening</h3>

<p>On the server:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>ss <span class="nt">-tulnp</span> | <span class="nb">grep </span>7777
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Expected output:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>tcp  LISTEN  0  100  0.0.0.0:7777  0.0.0.0:*  users:(("FactoryServer",pid=xxx,fd=x))
udp  UNCONN  0  0    0.0.0.0:7777  0.0.0.0:*  users:(("FactoryServer",pid=xxx,fd=x))
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If nothing appears, the game server process is not running. Check Docker logs for errors.</p>

<h3 id="check-ufw-allows-port-7777">Check UFW allows port 7777</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>ufw status | <span class="nb">grep </span>7777
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Expected:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>7777/tcp  ALLOW Anywhere
7777/udp  ALLOW Anywhere
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If missing:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>ufw allow 7777/tcp
<span class="nb">sudo </span>ufw allow 7777/udp
<span class="nb">sudo </span>ufw reload
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="test-the-vanilla-api-from-an-external-machine">Test the vanilla API (from an external machine)</h3>

<p>Run this from your local PC (not from the server itself):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-k</span> <span class="nt">-X</span> POST https://YOUR_PUBLIC_IP:7777/api/v1 <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"function":"HealthCheck","data":{}}'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<ul>
  <li>✅ <code class="language-plaintext highlighter-rouge">{"data":{"health":"healthy"}}</code> — Port 7777 is accessible externally. Proceed to Step 2.</li>
  <li>❌ <code class="language-plaintext highlighter-rouge">Connection refused</code> — The game server is not running or port 7777 is blocked externally.</li>
  <li>❌ <code class="language-plaintext highlighter-rouge">Connection timed out</code> — Port 7777 is blocked by a firewall (cloud security group or VPS provider firewall).</li>
  <li>❌ <code class="language-plaintext highlighter-rouge">SSL: certificate verify failed</code> — Normal. Add <code class="language-plaintext highlighter-rouge">-k</code> flag to ignore the self-signed certificate.</li>
</ul>

<hr />

<h2 id="step-2-verify-the-admin-password">Step 2: Verify the Admin Password</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-k</span> <span class="nt">-X</span> POST https://YOUR_PUBLIC_IP:7777/api/v1 <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "function": "PasswordLogin",
    "data": {
      "minimumPrivilegeLevel": "Administrator",
      "password": "YourAdminPassword"
    }
  }'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<ul>
  <li>✅ <code class="language-plaintext highlighter-rouge">{"data":{"authenticationToken":"..."}}</code> — Password is correct. Proceed to Step 3.</li>
  <li>❌ <code class="language-plaintext highlighter-rouge">{"errorCode":"wrong_password"}</code> — Incorrect password.</li>
</ul>

<p><strong>Fix for wrong password:</strong></p>
<ol>
  <li>Connect from game client</li>
  <li>Go to <strong>Escape → Server Settings → Administration</strong></li>
  <li>Reset the admin password</li>
  <li>Update the password in FICSIT.monitor</li>
</ol>

<p>If you’ve forgotten the admin password and cannot connect from the game client:</p>
<ol>
  <li>Stop the container: <code class="language-plaintext highlighter-rouge">docker stop satisfactory-server</code></li>
  <li>Open <code class="language-plaintext highlighter-rouge">./data/config/GameUserSettings.ini</code></li>
  <li>Find <code class="language-plaintext highlighter-rouge">AdminPassword=</code> and reset it</li>
  <li>Start the container: <code class="language-plaintext highlighter-rouge">docker start satisfactory-server</code></li>
</ol>

<hr />

<h2 id="step-3-verify-frm-is-running">Step 3: Verify FRM is Running</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>curl http://YOUR_PUBLIC_IP:8080/getPower
</pre></td></tr></tbody></table></code></pre></div></div>

<ul>
  <li>✅ JSON array (may be empty <code class="language-plaintext highlighter-rouge">[]</code>) — FRM is running. If <code class="language-plaintext highlighter-rouge">[]</code>, no session is active yet (start a game).</li>
  <li>❌ <code class="language-plaintext highlighter-rouge">Connection refused</code> — FRM is not running or port 8080 is blocked.</li>
  <li>❌ <code class="language-plaintext highlighter-rouge">Connection timed out</code> — Port 8080 is blocked by the cloud firewall.</li>
</ul>

<p><strong>Fix for FRM not accessible:</strong></p>

<ol>
  <li>Open port 8080 in UFW:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>ufw allow 8080/tcp
<span class="nb">sudo </span>ufw allow 8081/tcp
<span class="nb">sudo </span>ufw reload
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>
    <p>If using a cloud provider, open ports 8080 and 8081 TCP in the cloud firewall / security group.</p>
  </li>
  <li>Verify FRM is installed. From outside the game, you can check via the vanilla API:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-k</span> <span class="nt">-X</span> POST https://YOUR_PUBLIC_IP:7777/api/v1 <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"function":"frm","endpoint":"getPower"}'</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
</ol>

<p>If FRM is installed but port 8080 is blocked, this alternative endpoint still works.</p>

<hr />

<h2 id="step-4-cloud-provider-firewall-checks">Step 4: Cloud Provider Firewall Checks</h2>

<p>If UFW is configured correctly but external connections still fail, your cloud provider
may have a separate firewall that overrides UFW.</p>

<p><strong>Hetzner Cloud:</strong></p>
<ul>
  <li>Go to Cloud Console → Firewalls</li>
  <li>Verify your server’s firewall includes inbound rules for TCP 7777, UDP 7777, TCP 8888, TCP 8080, TCP 8081</li>
</ul>

<p><strong>DigitalOcean:</strong></p>
<ul>
  <li>Go to Networking → Firewalls</li>
  <li>Verify the Droplet’s firewall allows the required ports</li>
</ul>

<p><strong>AWS:</strong></p>
<ul>
  <li>Go to EC2 → Security Groups</li>
  <li>Verify inbound rules include TCP 7777, UDP 7777, TCP 8888, TCP 8080, TCP 8081 from <code class="language-plaintext highlighter-rouge">0.0.0.0/0</code></li>
</ul>

<hr />

<h2 id="step-5-verify-the-ip-is-public">Step 5: Verify the IP is Public</h2>

<p>FICSIT.monitor’s servers connect to your server from the internet. Common mistake: using
a private or local IP.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="c"># Check your server's public IP</span>
curl ifconfig.me
<span class="c"># or</span>
curl api.ipify.org
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The public IP shown here is what you should enter in FICSIT.monitor — not <code class="language-plaintext highlighter-rouge">localhost</code>,
not <code class="language-plaintext highlighter-rouge">192.168.x.x</code>, not <code class="language-plaintext highlighter-rouge">10.x.x.x</code>.</p>

<hr />

<h2 id="still-not-working">Still Not Working?</h2>

<p>Gather this diagnostic information before seeking support:</p>

<ol>
  <li>Output of <code class="language-plaintext highlighter-rouge">docker ps | grep satisfactory</code></li>
  <li>Output of <code class="language-plaintext highlighter-rouge">curl -k -X POST https://YOUR_IP:7777/api/v1 -H "Content-Type: application/json" -d '{"function":"HealthCheck","data":{}}'</code></li>
  <li>Output of <code class="language-plaintext highlighter-rouge">curl http://YOUR_IP:8080/getPower</code></li>
  <li>Output of <code class="language-plaintext highlighter-rouge">sudo ufw status</code></li>
  <li>The exact error code shown in FICSIT.monitor (422, 502, or 500)</li>
</ol>]]></content><author><name></name></author><category term="troubleshooting" /><category term="satisfactory" /><category term="ficsitmonitor" /><category term="connection" /><category term="firewall" /><category term="troubleshooting" /><category term="diagnosis" /><summary type="html"><![CDATA[Step-by-step guide to diagnose why FICSIT.monitor can't connect to your Satisfactory server. Check firewall, ports, TLS, FRM availability, and common fixes.]]></summary></entry><entry><title type="html">Common Errors &amp;amp; Solutions</title><link href="https://apptolast.github.io/FicsitDocumentation/posts/troubleshooting/" rel="alternate" type="text/html" title="Common Errors &amp;amp; Solutions" /><published>2026-04-15T02:26:00+02:00</published><updated>2026-04-16T01:01:21+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/posts/troubleshooting</id><content type="html" xml:base="https://apptolast.github.io/FicsitDocumentation/posts/troubleshooting/"><![CDATA[<h2 id="overview">Overview</h2>

<p>This page covers the most common errors encountered when setting up or using
FICSIT.monitor, with their causes and step-by-step fixes.</p>

<hr />

<h2 id="ficsitmonitor-error-codes">FICSIT.monitor Error Codes</h2>

<h3 id="422--wrong-admin-password">422 — Wrong Admin Password</h3>

<p><strong>Error:</strong> <code class="language-plaintext highlighter-rouge">InvalidAdminPasswordException</code></p>

<p><strong>Cause:</strong> The admin password you provided when adding the server does not match
the password set on the Satisfactory server.</p>

<p><strong>Fix:</strong></p>
<ol>
  <li>Connect to your server from the game client</li>
  <li>Go to <strong>Escape → Server Settings → Administration</strong></li>
  <li>Note the admin password (or reset it to a known value)</li>
  <li>In FICSIT.monitor, go to your server → Edit → update the admin password</li>
  <li>Retry adding or reconnecting the server</li>
</ol>

<p><strong>Verify the password manually:</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-k</span> <span class="nt">-X</span> POST https://YOUR_IP:7777/api/v1 <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"function":"PasswordLogin","data":{"minimumPrivilegeLevel":"Administrator","password":"YOUR_PASSWORD"}}'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If this returns <code class="language-plaintext highlighter-rouge">{"errorCode":"wrong_password"}</code>, the password is incorrect.</p>

<hr />

<h3 id="502--server-unreachable">502 — Server Unreachable</h3>

<p><strong>Error:</strong> <code class="language-plaintext highlighter-rouge">ServerUnreachableException</code></p>

<p><strong>Cause:</strong> FICSIT.monitor cannot establish a TCP connection to your server on port 7777.</p>

<p><strong>Fix checklist:</strong></p>

<ol>
  <li>Verify the server container is running:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>docker ps | <span class="nb">grep </span>satisfactory
<span class="c"># Should show "Up X hours"</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Verify the server is listening on port 7777:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>ss <span class="nt">-tulnp</span> | <span class="nb">grep </span>7777
<span class="c"># Should show: 0.0.0.0:7777</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Verify the port is open in UFW:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>ufw status | <span class="nb">grep </span>7777
<span class="c"># Should show: 7777/tcp ALLOW Anywhere</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Test from an external machine:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-k</span> <span class="nt">-X</span> POST https://YOUR_PUBLIC_IP:7777/api/v1 <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"function":"HealthCheck","data":{}}'</span>
<span class="c"># Expected: {"data":{"health":"healthy"}}</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Verify the IP in FICSIT.monitor is the <strong>public</strong> IP — not <code class="language-plaintext highlighter-rouge">localhost</code>, <code class="language-plaintext highlighter-rouge">127.0.0.1</code>,
or a private IP like <code class="language-plaintext highlighter-rouge">192.168.x.x</code>.</li>
</ol>

<hr />

<h3 id="500--provisioning-failed">500 — Provisioning Failed</h3>

<p><strong>Error:</strong> <code class="language-plaintext highlighter-rouge">ProvisioningFailedException</code></p>

<p><strong>Cause:</strong> FICSIT.monitor connected to the server (port 7777 is OK), authenticated
(password is correct), but something failed during the setup phase. Usually caused by
FRM not being accessible on port 8080.</p>

<p><strong>Fix checklist:</strong></p>

<ol>
  <li>Verify FRM is running:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>curl http://YOUR_PUBLIC_IP:8080/getPower
<span class="c"># Expected: JSON array</span>
<span class="c"># If "Connection refused": FRM not running or port 8080 not open</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Open port 8080 in UFW:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>ufw allow 8080/tcp <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>ufw reload
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Verify FRM is installed (restart server after installing):
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>docker restart satisfactory-server
<span class="c"># Wait ~2 minutes for the server to come back online</span>
curl http://YOUR_PUBLIC_IP:8080/getPower
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
</ol>

<hr />

<h2 id="frm-specific-issues">FRM-Specific Issues</h2>

<h3 id="frm-shows-no-data-empty-array">FRM Shows No Data (Empty Array)</h3>

<p><strong>Symptom:</strong> <code class="language-plaintext highlighter-rouge">curl http://YOUR_IP:8080/getPower</code> returns <code class="language-plaintext highlighter-rouge">[]</code></p>

<p><strong>Cause:</strong> The server is running but no game session is active (no world loaded).</p>

<p><strong>Fix:</strong> Connect from the game client and start or load a game session.</p>

<h3 id="frm-not-installed">FRM Not Installed</h3>

<p><strong>Symptom:</strong> <code class="language-plaintext highlighter-rouge">curl http://YOUR_IP:8080/getPower</code> returns <code class="language-plaintext highlighter-rouge">Connection refused</code></p>

<p><strong>Fix:</strong></p>
<ol>
  <li>Install FRM via Satisfactory Mod Manager (see <a href="/FicsitDocumentation/posts/frm-installation/">Installing FRM</a>)</li>
  <li>Restart the server container: <code class="language-plaintext highlighter-rouge">docker restart satisfactory-server</code></li>
  <li>Wait for the server to come back online and retry</li>
</ol>

<h3 id="frm-data-stops-updating">FRM Data Stops Updating</h3>

<p><strong>Symptom:</strong> Dashboard panels show data, but it’s not changing</p>

<p><strong>Cause:</strong> The background polling jobs may have stopped.</p>

<p><strong>Fix:</strong> Check the Laravel Horizon queue worker status. In Kubernetes, check the
Horizon deployment pod is running. In Docker, this would require the full application
stack (not just the game server).</p>

<hr />

<h2 id="dashboard-issues">Dashboard Issues</h2>

<h3 id="dashboard-shows-stale-data">Dashboard Shows Stale Data</h3>

<p><strong>Symptom:</strong> The dashboard isn’t updating automatically</p>

<p><strong>Cause:</strong> The WebSocket (Reverb) connection has been lost.</p>

<p><strong>Fix:</strong></p>
<ol>
  <li>Refresh the page — this will reconnect to the WebSocket</li>
  <li>Check your browser’s network tab for WebSocket errors</li>
  <li>Verify the Reverb service is running (for self-hosted deployments)</li>
</ol>

<h3 id="server-shows-as-degraded">Server Shows as “Degraded”</h3>

<p><strong>Symptom:</strong> Server status is yellow “Degraded”</p>

<p><strong>Cause:</strong> <code class="language-plaintext highlighter-rouge">tick_rate</code> dropped below the normal threshold (~15 tick/s).</p>

<p><strong>Meaning:</strong> The server is responding but running slowly. Your factory may be
too complex for the current VPS specs.</p>

<p><strong>Options:</strong></p>
<ul>
  <li>Reduce factory complexity (merge/simplify production lines)</li>
  <li>Upgrade to a VPS with more CPU cores</li>
  <li>Enable underclocking on machines to reduce simulation load</li>
</ul>

<h3 id="power-panel-shows-no-data">Power Panel Shows No Data</h3>

<p><strong>Symptom:</strong> Power panel is empty but other panels work</p>

<p><strong>Cause:</strong> Your factory has no active power circuits (no generators placed yet),
or you’re very early in the game with no electrical infrastructure.</p>

<p>This is normal for early-game sessions. The panel populates once you place generators
and connect machines to a power grid.</p>

<hr />

<h2 id="docker-issues">Docker Issues</h2>

<h3 id="server-stopped-after-restart">Server Stopped After Restart</h3>

<p><strong>Symptom:</strong> Server container stops after system reboot</p>

<p><strong>Cause:</strong> The container wasn’t started with <code class="language-plaintext highlighter-rouge">--restart unless-stopped</code>.</p>

<p><strong>Fix:</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="c"># Stop and remove the old container</span>
docker stop satisfactory-server <span class="o">&amp;&amp;</span> docker <span class="nb">rm </span>satisfactory-server

<span class="c"># Recreate with restart policy</span>
docker run <span class="nt">-d</span> <span class="se">\</span>
  <span class="nt">--name</span> satisfactory-server <span class="se">\</span>
  <span class="nt">--restart</span> unless-stopped <span class="se">\</span>
  <span class="o">[</span>... rest of your docker run <span class="nb">command</span> ...]
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Or if using Compose, Docker Compose services restart automatically if <code class="language-plaintext highlighter-rouge">restart: unless-stopped</code>
is set (which it is in the guide’s example).</p>

<h3 id="container-exits-immediately">Container Exits Immediately</h3>

<p><strong>Symptom:</strong> <code class="language-plaintext highlighter-rouge">docker ps</code> shows the container stopped</p>

<p><strong>Cause:</strong> Usually an error during startup (permissions, missing files, etc.)</p>

<p><strong>Fix:</strong> Check logs immediately after the container exits:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>docker logs satisfactory-server
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Look for errors like <code class="language-plaintext highlighter-rouge">permission denied</code> (PUID/PGID mismatch) or SteamCMD failures.</p>]]></content><author><name></name></author><category term="troubleshooting" /><category term="satisfactory" /><category term="ficsitmonitor" /><category term="troubleshooting" /><category term="errors" /><category term="error-codes" /><summary type="html"><![CDATA[Troubleshoot FICSIT.monitor errors: 422 admin password, 502 server unreachable, 500 provisioning failed, FRM not responding, and stale data issues.]]></summary></entry><entry><title type="html">Frequently Asked Questions</title><link href="https://apptolast.github.io/FicsitDocumentation/posts/faq/" rel="alternate" type="text/html" title="Frequently Asked Questions" /><published>2026-04-15T02:25:00+02:00</published><updated>2026-04-16T01:01:21+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/posts/faq</id><content type="html" xml:base="https://apptolast.github.io/FicsitDocumentation/posts/faq/"><![CDATA[<h2 id="general">General</h2>

<h3 id="do-i-need-the-frm-mod-to-use-ficsitmonitor">Do I need the FRM mod to use FICSIT.monitor?</h3>

<p>The FRM mod is required for most monitoring panels. Without FRM, FICSIT.monitor
can only show:</p>
<ul>
  <li>Server status (online/offline/degraded)</li>
  <li>Tick rate</li>
  <li>Player count</li>
  <li>Tech tier and game phase</li>
</ul>

<p>All other panels (power grid, production, players with positions, trains, drones,
factory buildings) require FRM installed and running.</p>

<p>→ See <a href="/FicsitDocumentation/posts/frm-installation/">Installing FRM</a></p>

<h3 id="what-version-of-satisfactory-does-the-dedicated-server-require">What version of Satisfactory does the dedicated server require?</h3>

<p>Satisfactory 1.0 or later. FICSIT.monitor uses the HTTPS API introduced in 1.0.
Experimental branch support depends on Coffee Stain Studios maintaining API compatibility
between releases.</p>

<h3 id="can-i-run-ficsitmonitor-on-a-windows-machine-instead-of-ubuntu">Can I run FICSIT.monitor on a Windows machine instead of Ubuntu?</h3>

<p>The documentation targets Ubuntu. Docker is available for Windows, so technically
you could run the <code class="language-plaintext highlighter-rouge">wolveix/satisfactory-server</code> image on Windows using Docker Desktop,
but this is not tested or supported.</p>

<p>For VPS hosting, Ubuntu 22.04 or 24.04 LTS is strongly recommended.</p>

<h3 id="does-ficsitmonitor-store-my-admin-password">Does FICSIT.monitor store my admin password?</h3>

<p>Yes, encrypted (Laravel encryption using the APP_KEY). The password is used to
re-authenticate when the API token expires. It is never exposed via the API or
any dashboard interface.</p>

<hr />

<h2 id="ports">Ports</h2>

<h3 id="what-ports-do-i-need-to-open">What ports do I need to open?</h3>

<table>
  <thead>
    <tr>
      <th>Port</th>
      <th>Protocol</th>
      <th>Required for</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>7777</td>
      <td>TCP + UDP</td>
      <td>Game + Vanilla API</td>
    </tr>
    <tr>
      <td>8888</td>
      <td>TCP</td>
      <td>Reliable messaging (Patch 1.1+)</td>
    </tr>
    <tr>
      <td>8080</td>
      <td>TCP</td>
      <td>FRM HTTP API</td>
    </tr>
    <tr>
      <td>8081</td>
      <td>TCP</td>
      <td>FRM WebSocket</td>
    </tr>
  </tbody>
</table>

<p>All four must be open for full FICSIT.monitor functionality.</p>

<h3 id="should-i-open-ports-15000-or-15777">Should I open ports 15000 or 15777?</h3>

<p>No. These ports were used in Satisfactory early access and are deprecated since
Patch 1.0. Do not open them.</p>

<h3 id="can-i-use-port-forwarding-nat">Can I use port forwarding (NAT)?</h3>

<p>No. The Satisfactory dedicated server binds to its external IP address. The
external port must equal the internal port. NAT-based port forwarding is not supported.</p>

<h3 id="im-on-a-home-server--can-i-use-ficsitmonitor">I’m on a home server — can I use FICSIT.monitor?</h3>

<p>Yes, if your ISP provides a public IP and you can open the required ports. However,
most residential ISPs assign dynamic IPs — you would need a dynamic DNS service
(like DuckDNS or No-IP) and update FICSIT.monitor when your IP changes.</p>

<hr />

<h2 id="ficsitmonitor">FICSIT.monitor</h2>

<h3 id="why-does-the-dashboard-show-server-offline">Why does the dashboard show “Server Offline”?</h3>

<p>FICSIT.monitor could not reach your server at <code class="language-plaintext highlighter-rouge">host:7777</code>. Check:</p>
<ol>
  <li>The server container is running: <code class="language-plaintext highlighter-rouge">docker ps | grep satisfactory</code></li>
  <li>Port 7777 TCP is open in your firewall</li>
  <li>The IP address in FICSIT.monitor is the public IP (not localhost or 127.0.0.1)</li>
</ol>

<p>→ See <a href="/FicsitDocumentation/posts/connection-errors/">Diagnosing Connection Issues</a></p>

<h3 id="frm-data-is-not-showing-in-the-dashboard">FRM data is not showing in the dashboard</h3>

<p>If server status shows online but all FRM panels show no data:</p>
<ol>
  <li>Verify FRM is installed: <code class="language-plaintext highlighter-rouge">curl http://YOUR_IP:8080/getPower</code></li>
  <li>If connection refused: port 8080 is blocked — open it in your firewall</li>
  <li>If FRM responds but data is empty: the mod may need a restart
(restart the game server container: <code class="language-plaintext highlighter-rouge">docker restart satisfactory-server</code>)</li>
</ol>

<h3 id="what-does-the-422-error-mean-when-adding-a-server">What does the 422 error mean when adding a server?</h3>

<p><code class="language-plaintext highlighter-rouge">InvalidAdminPasswordException</code> — the admin password you entered is incorrect.
Verify the password you set in the game’s <strong>Server Settings → Administration</strong> tab.</p>

<h3 id="what-does-the-502-error-mean-when-adding-a-server">What does the 502 error mean when adding a server?</h3>

<p><code class="language-plaintext highlighter-rouge">ServerUnreachableException</code> — FICSIT.monitor’s servers cannot connect to your
server at the specified host and port. Check:</p>
<ol>
  <li>Port 7777 TCP is open externally</li>
  <li>The host IP is the public IP, not a private/local IP</li>
  <li>The server container is actually running</li>
</ol>

<h3 id="how-often-are-metrics-updated">How often are metrics updated?</h3>

<table>
  <thead>
    <tr>
      <th>Metric</th>
      <th>Interval</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Server health, player count</td>
      <td>10 seconds</td>
    </tr>
    <tr>
      <td>Power grid, players (with FRM)</td>
      <td>15 seconds</td>
    </tr>
    <tr>
      <td>Production, trains, drones</td>
      <td>30 seconds</td>
    </tr>
    <tr>
      <td>Factory buildings</td>
      <td>60 seconds</td>
    </tr>
  </tbody>
</table>

<p>On the Free plan, all metrics are polled every 30 seconds instead.</p>

<h3 id="how-many-servers-can-i-monitor">How many servers can I monitor?</h3>

<table>
  <thead>
    <tr>
      <th>Plan</th>
      <th>Servers</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Free</td>
      <td>1</td>
    </tr>
    <tr>
      <td>Hobby</td>
      <td>2</td>
    </tr>
    <tr>
      <td>Pro</td>
      <td>5</td>
    </tr>
    <tr>
      <td>Team</td>
      <td>15</td>
    </tr>
    <tr>
      <td>Enterprise</td>
      <td>Unlimited</td>
    </tr>
  </tbody>
</table>

<h3 id="how-long-is-metric-history-retained">How long is metric history retained?</h3>

<table>
  <thead>
    <tr>
      <th>Plan</th>
      <th>Retention</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Free</td>
      <td>24 hours</td>
    </tr>
    <tr>
      <td>Hobby</td>
      <td>7 days</td>
    </tr>
    <tr>
      <td>Pro</td>
      <td>30 days</td>
    </tr>
    <tr>
      <td>Team</td>
      <td>90 days</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="server-performance">Server Performance</h2>

<h3 id="what-is-the-minimum-vps-spec">What is the minimum VPS spec?</h3>

<ul>
  <li>4 vCPU (8 recommended)</li>
  <li>12 GB RAM (16 recommended)</li>
  <li>20 GB disk (75 recommended)</li>
  <li>Ubuntu 22.04 or 24.04 LTS</li>
</ul>

<p>The game server consumes more RAM as your factory grows. Monitor RAM usage with
<code class="language-plaintext highlighter-rouge">docker stats satisfactory-server</code> and upgrade your VPS before you hit the limit.</p>

<h3 id="why-is-the-server-showing-degraded-status">Why is the server showing “Degraded” status?</h3>

<p><code class="language-plaintext highlighter-rouge">Degraded</code> means the tick rate is below the normal threshold (~15 tick/s or lower).
Common causes:</p>
<ul>
  <li>Factory is too large for the server’s CPU</li>
  <li>The VPS is under-provisioned for the current factory size</li>
  <li>A specific game mechanic (fluid simulation, large train networks) is CPU-intensive</li>
</ul>

<p>Solutions: reduce factory complexity, upgrade to a more powerful VPS, or split the
factory across multiple sessions.</p>

<h3 id="the-server-crashed-and-wont-restart">The server crashed and won’t restart</h3>

<p>Check the Docker logs: <code class="language-plaintext highlighter-rouge">docker logs satisfactory-server --tail 50</code></p>

<p>Common causes:</p>
<ul>
  <li>Out of RAM — upgrade the VPS</li>
  <li>Corrupted save file — restore from backup in <code class="language-plaintext highlighter-rouge">./data/saves/</code></li>
  <li>Docker stopped — <code class="language-plaintext highlighter-rouge">docker start satisfactory-server</code></li>
</ul>]]></content><author><name></name></author><category term="troubleshooting" /><category term="satisfactory" /><category term="ficsitmonitor" /><category term="faq" /><category term="help" /><category term="troubleshooting" /><category term="ports" /><summary type="html"><![CDATA[Common questions about FICSIT.monitor and Satisfactory dedicated servers. FRM data not showing, connection errors, port requirements, and server specs.]]></summary></entry><entry><title type="html">Plans &amp;amp; Pricing</title><link href="https://apptolast.github.io/FicsitDocumentation/posts/pricing-plans/" rel="alternate" type="text/html" title="Plans &amp;amp; Pricing" /><published>2026-04-15T02:24:00+02:00</published><updated>2026-04-15T02:24:00+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/posts/pricing-plans</id><content type="html" xml:base="https://apptolast.github.io/FicsitDocumentation/posts/pricing-plans/"><![CDATA[<h2 id="overview">Overview</h2>

<p>FICSIT.monitor offers five subscription tiers designed to fit different server sizes
and monitoring needs — from a single personal server to enterprise-scale deployments.</p>

<hr />

<h2 id="plan-comparison">Plan Comparison</h2>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Free</th>
      <th>Hobby</th>
      <th>Pro</th>
      <th>Team</th>
      <th>Enterprise</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Price (monthly)</strong></td>
      <td>0€</td>
      <td>6€</td>
      <td>19€</td>
      <td>49€</td>
      <td>Custom</td>
    </tr>
    <tr>
      <td><strong>Price (annual)</strong></td>
      <td>0€</td>
      <td>58€</td>
      <td>182€</td>
      <td>470€</td>
      <td>Custom</td>
    </tr>
    <tr>
      <td><strong>Annual savings</strong></td>
      <td>—</td>
      <td>~20%</td>
      <td>~20%</td>
      <td>~20%</td>
      <td>—</td>
    </tr>
    <tr>
      <td><strong>Servers</strong></td>
      <td>1</td>
      <td>2</td>
      <td>5</td>
      <td>15</td>
      <td>Unlimited</td>
    </tr>
    <tr>
      <td><strong>Data retention</strong></td>
      <td>24h</td>
      <td>7 days</td>
      <td>30 days</td>
      <td>90 days</td>
      <td>Custom</td>
    </tr>
    <tr>
      <td><strong>Polling interval</strong></td>
      <td>30s</td>
      <td>15s</td>
      <td>10s</td>
      <td>10s</td>
      <td>10s</td>
    </tr>
    <tr>
      <td><strong>Discord alerts</strong></td>
      <td>❌</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td><strong>Multi-channel alerts</strong></td>
      <td>❌</td>
      <td>❌</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td><strong>Public API</strong></td>
      <td>❌</td>
      <td>❌</td>
      <td>✅ (10k req/day)</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td><strong>Shareable dashboards</strong></td>
      <td>❌</td>
      <td>❌</td>
      <td>✅</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td><strong>Multi-user access</strong></td>
      <td>❌</td>
      <td>❌</td>
      <td>❌</td>
      <td>✅</td>
      <td>✅</td>
    </tr>
    <tr>
      <td><strong>SSO</strong></td>
      <td>❌</td>
      <td>❌</td>
      <td>❌</td>
      <td>Optional</td>
      <td>✅</td>
    </tr>
    <tr>
      <td><strong>Uptime SLA</strong></td>
      <td>None</td>
      <td>None</td>
      <td>None</td>
      <td>99.9%</td>
      <td>99.99%</td>
    </tr>
    <tr>
      <td><strong>Support</strong></td>
      <td>Community</td>
      <td>Community</td>
      <td>Email (&lt;24h)</td>
      <td>Priority</td>
      <td>Dedicated</td>
    </tr>
  </tbody>
</table>

<blockquote class="prompt-info">
  <p>Prices shown exclude IVA/VAT where applicable. Indicative pricing during beta.</p>
</blockquote>

<hr />

<h2 id="free">Free</h2>

<p><strong>0€/month</strong> — No credit card required.</p>

<ul>
  <li>1 server</li>
  <li>24 hours of metric history</li>
  <li>30-second polling interval</li>
  <li>Server status, power, production, players, trains, drones panels</li>
  <li>No alerts, no API access, no historical charts beyond 24h</li>
</ul>

<p>Best for: trying FICSIT.monitor or monitoring a single personal server where
24-hour history is sufficient.</p>

<hr />

<h2 id="hobby">Hobby</h2>

<p><strong>6€/month</strong> (58€/year — ~20% off)</p>

<p>Everything in Free, plus:</p>
<ul>
  <li>2 servers</li>
  <li>7 days of metric history</li>
  <li>15-second polling interval (double the frequency of Free)</li>
  <li>Discord alert notifications</li>
</ul>

<p>Best for: small groups of friends wanting recent history and basic Discord notifications.</p>

<hr />

<h2 id="pro">Pro</h2>

<p><strong>19€/month</strong> (182€/year — ~20% off)</p>

<p>Everything in Hobby, plus:</p>
<ul>
  <li>5 servers</li>
  <li>30 days of metric history</li>
  <li>10-second polling interval</li>
  <li>Multi-channel alerts (Discord + additional channels)</li>
  <li><strong>Public REST API</strong> access (10,000 requests/day)</li>
  <li><strong>Shareable dashboard links</strong> — share a read-only dashboard view with others</li>
  <li>Twitch/YouTube overlay widget</li>
  <li>Email support (response within 24 hours)</li>
</ul>

<p>Best for: active server admins, content creators, and small community servers who
want historical charts, API integrations, and sharing capabilities.</p>

<hr />

<h2 id="team">Team</h2>

<p><strong>49€/month</strong> (470€/year — ~20% off)</p>

<p>Everything in Pro, plus:</p>
<ul>
  <li>15 servers</li>
  <li>90 days of metric history</li>
  <li><strong>Multi-user access with roles</strong> — Admin, Operator, Viewer</li>
  <li>Optional SSO integration</li>
  <li>99.9% uptime SLA</li>
  <li>Priority support</li>
</ul>

<p>Best for: gaming communities, clans, or organizations running multiple servers and
needing team-based access control.</p>

<hr />

<h2 id="enterprise">Enterprise</h2>

<p><strong>Custom pricing</strong> — contact us.</p>

<p>Everything in Team, plus:</p>
<ul>
  <li>Unlimited servers</li>
  <li>Custom data retention period</li>
  <li>On-premises or private Kubernetes deployment option</li>
  <li>99.99% uptime SLA</li>
  <li>Dedicated support channel</li>
  <li>Custom security audit and compliance review</li>
  <li>Custom API rate limits</li>
</ul>

<p>Best for: large communities, server hosting providers, or organizations with
specific compliance or infrastructure requirements.</p>

<hr />

<h2 id="annual-billing">Annual Billing</h2>

<p>Annual plans provide approximately <strong>20% discount</strong> (equivalent to 2 free months):</p>

<table>
  <thead>
    <tr>
      <th>Plan</th>
      <th>Monthly</th>
      <th>Annual</th>
      <th>Savings</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Hobby</td>
      <td>6€/mo</td>
      <td>58€/yr</td>
      <td>14€</td>
    </tr>
    <tr>
      <td>Pro</td>
      <td>19€/mo</td>
      <td>182€/yr</td>
      <td>46€</td>
    </tr>
    <tr>
      <td>Team</td>
      <td>49€/mo</td>
      <td>470€/yr</td>
      <td>118€</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="frequently-asked-questions">Frequently Asked Questions</h2>

<p><strong>What happens to my data if I downgrade?</strong>
Data collected while on a higher tier is retained until it expires under your new
tier’s retention period. No data is deleted immediately on downgrade.</p>

<p><strong>Can I add more servers beyond my plan limit?</strong>
No — you must upgrade to a higher tier to add more servers.</p>

<p><strong>Is there a free trial for paid plans?</strong>
Contact us for trial access to Pro or Team plans.</p>

<p><strong>What counts as a “server”?</strong>
Each unique Satisfactory dedicated server IP + port combination counts as one server.
Multiple instances on different ports of the same machine count as separate servers.</p>

<hr />

<h2 id="getting-started">Getting Started</h2>

<p>Sign up for free at <a href="https://satisfactory-dashboard.pablohgdev.com">satisfactory-dashboard.pablohgdev.com</a>.
No credit card required for the Free plan.</p>]]></content><author><name></name></author><category term="pricing" /><category term="satisfactory" /><category term="ficsitmonitor" /><category term="pricing" /><category term="saas" /><category term="plans" /><category term="tiers" /><summary type="html"><![CDATA[FICSIT.monitor subscription plans: Free, Hobby, Pro, Team, and Enterprise. Compare server limits, data retention, polling intervals, and included features.]]></summary></entry><entry><title type="html">Metrics API Reference</title><link href="https://apptolast.github.io/FicsitDocumentation/posts/api-metrics/" rel="alternate" type="text/html" title="Metrics API Reference" /><published>2026-04-15T02:23:00+02:00</published><updated>2026-04-16T01:01:21+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/posts/api-metrics</id><content type="html" xml:base="https://apptolast.github.io/FicsitDocumentation/posts/api-metrics/"><![CDATA[<h2 id="overview">Overview</h2>

<p>The Metrics API provides access to all real-time data FICSIT.monitor collects from
your Satisfactory server. All endpoints require authentication and a valid server UUID.</p>

<p><strong>Base path:</strong> <code class="language-plaintext highlighter-rouge">/api/v1/servers/{server_id}/</code></p>

<p>Replace <code class="language-plaintext highlighter-rouge">{server_id}</code> with the UUID from <code class="language-plaintext highlighter-rouge">GET /api/v1/servers</code>.</p>

<hr />

<h2 id="get-metricslatest">GET /metrics/latest</h2>

<p>Latest server state (health, player count, game phase).</p>

<p><strong>Updates every 10 seconds.</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/metrics/latest <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Response:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"time"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"tick_rate"</span><span class="p">:</span><span class="w"> </span><span class="mf">30.1</span><span class="p">,</span><span class="w">
  </span><span class="nl">"player_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
  </span><span class="nl">"tech_tier"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
  </span><span class="nl">"game_phase"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PhaseTwo"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"is_running"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"is_paused"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
  </span><span class="nl">"total_duration"</span><span class="p">:</span><span class="w"> </span><span class="mi">86400</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Field</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">tick_rate</code></td>
      <td>Server game loop speed. Healthy = ~30 tick/s</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">player_count</code></td>
      <td>Currently connected players</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">tech_tier</code></td>
      <td>Research tier (0–9)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">game_phase</code></td>
      <td>Current game progression phase</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">is_running</code></td>
      <td><code class="language-plaintext highlighter-rouge">true</code> if a session is active</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">is_paused</code></td>
      <td><code class="language-plaintext highlighter-rouge">true</code> if the session is paused</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">total_duration</code></td>
      <td>Total session time in seconds</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="get-players">GET /players</h2>

<p>All players and their current status.</p>

<p><strong>Updates every 15 seconds (FRM required).</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/players <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Response:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"uuid"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PlayerName"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"is_online"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"health"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.85</span><span class="p">,</span><span class="w">
    </span><span class="nl">"speed"</span><span class="p">:</span><span class="w"> </span><span class="mf">45.2</span><span class="p">,</span><span class="w">
    </span><span class="nl">"is_dead"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"pos_x"</span><span class="p">:</span><span class="w"> </span><span class="mf">12450.5</span><span class="p">,</span><span class="w">
    </span><span class="nl">"pos_y"</span><span class="p">:</span><span class="w"> </span><span class="mf">-34200.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"pos_z"</span><span class="p">:</span><span class="w"> </span><span class="mf">105.3</span><span class="p">,</span><span class="w">
    </span><span class="nl">"last_seen_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>See <a href="/FicsitDocumentation/posts/players-sessions/">Players &amp; Sessions Panel</a> for field descriptions.</p>

<hr />

<h2 id="get-powerlatest">GET /power/latest</h2>

<p>Latest power circuit data.</p>

<p><strong>Updates every 15 seconds (FRM required).</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/power/latest <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Response:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"circuit_group_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"power_production"</span><span class="p">:</span><span class="w"> </span><span class="mf">500.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"power_consumed"</span><span class="p">:</span><span class="w"> </span><span class="mf">420.5</span><span class="p">,</span><span class="w">
    </span><span class="nl">"power_capacity"</span><span class="p">:</span><span class="w"> </span><span class="mf">600.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"power_max_consumed"</span><span class="p">:</span><span class="w"> </span><span class="mf">650.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"battery_percent"</span><span class="p">:</span><span class="w"> </span><span class="mf">100.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"battery_capacity"</span><span class="p">:</span><span class="w"> </span><span class="mf">200.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"battery_differential"</span><span class="p">:</span><span class="w"> </span><span class="mf">79.5</span><span class="p">,</span><span class="w">
    </span><span class="nl">"fuse_triggered"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"time"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>See <a href="/FicsitDocumentation/posts/power-grid-panel/">Power Grid Panel</a> for field descriptions.</p>

<hr />

<h2 id="get-productionlatest">GET /production/latest</h2>

<p>Latest production and consumption rates per item.</p>

<p><strong>Updates every 30 seconds (FRM required).</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/production/latest <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Response:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"item_class_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Desc_IronIngot_C"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"item_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Iron Ingot"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"item_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"item"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"current_prod"</span><span class="p">:</span><span class="w"> </span><span class="mf">900.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"max_prod"</span><span class="p">:</span><span class="w"> </span><span class="mf">900.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"current_consumed"</span><span class="p">:</span><span class="w"> </span><span class="mf">720.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"max_consumed"</span><span class="p">:</span><span class="w"> </span><span class="mf">900.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"prod_percent"</span><span class="p">:</span><span class="w"> </span><span class="mf">100.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"cons_percent"</span><span class="p">:</span><span class="w"> </span><span class="mf">80.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"time"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>See <a href="/FicsitDocumentation/posts/production-panel/">Production Panel</a> for field descriptions.</p>

<hr />

<h2 id="get-trains">GET /trains</h2>

<p>All trains and their current state.</p>

<p><strong>Updates every 30 seconds (FRM required).</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/trains <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Response:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"uuid"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"frm_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Train_1"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Iron Express"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SelfDriving"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"forward_speed"</span><span class="p">:</span><span class="w"> </span><span class="mf">120.5</span><span class="p">,</span><span class="w">
    </span><span class="nl">"payload_mass"</span><span class="p">:</span><span class="w"> </span><span class="mf">32000.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"max_payload_mass"</span><span class="p">:</span><span class="w"> </span><span class="mf">48000.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"is_derailed"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"is_pending_derail"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"current_station"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Iron Mine Station"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"self_driving"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"power_consumed"</span><span class="p">:</span><span class="w"> </span><span class="mf">25.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"updated_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>See <a href="/FicsitDocumentation/posts/trains-drones/">Trains &amp; Drone Stations Panel</a>.</p>

<hr />

<h2 id="get-drones">GET /drones</h2>

<p>All drone stations.</p>

<p><strong>Updates every 30 seconds (FRM required).</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/drones <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Response:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"uuid"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"frm_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"DroneStation_1"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"North Outpost"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"drone_status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Flying"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"paired_station"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Main Hub"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"avg_round_trip_secs"</span><span class="p">:</span><span class="w"> </span><span class="mf">45.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"avg_inc_rate"</span><span class="p">:</span><span class="w"> </span><span class="mf">60.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"avg_out_rate"</span><span class="p">:</span><span class="w"> </span><span class="mf">60.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"power_consumed"</span><span class="p">:</span><span class="w"> </span><span class="mf">150.0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"updated_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>See <a href="/FicsitDocumentation/posts/trains-drones/">Trains &amp; Drone Stations Panel</a>.</p>

<hr />

<h2 id="get-generators">GET /generators</h2>

<p>Power generator data cached from FRM.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/generators <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Returns the raw generator data from FRM <code class="language-plaintext highlighter-rouge">getGenerators</code>, cached in Redis.</p>

<hr />

<h2 id="get-extractors">GET /extractors</h2>

<p>Resource extractor data cached from FRM.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/extractors <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Returns the raw extractor data from FRM <code class="language-plaintext highlighter-rouge">getExtractor</code> (singular — FRM endpoint name),
cached in Redis.</p>

<hr />

<h2 id="get-world-inventory">GET /world-inventory</h2>

<p>Global world inventory cached from FRM.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/world-inventory <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Returns the raw world inventory from FRM <code class="language-plaintext highlighter-rouge">getWorldInv</code>, cached in Redis.</p>

<hr />

<h2 id="get-resource-sink">GET /resource-sink</h2>

<p>AWESOME Sink data cached from FRM.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/resource-sink <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Returns the raw resource sink data from FRM <code class="language-plaintext highlighter-rouge">getResourceSink</code>, cached in Redis.</p>

<hr />

<h2 id="get-dashboard">GET /dashboard</h2>

<p>Full metrics snapshot — all panels in one request. Useful for initial page load
or building a complete monitoring view.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/<span class="o">{</span><span class="nb">id</span><span class="o">}</span>/dashboard <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Returns the combined data from all metric endpoints in a single response object.</p>

<hr />

<h2 id="notes">Notes</h2>

<ul>
  <li>Endpoints returning cached data (generators, extractors, world-inventory, resource-sink)
reflect the last poll cycle (every 15–60 seconds depending on the endpoint)</li>
  <li>All timestamps are ISO 8601 UTC</li>
  <li>Server UUID must belong to the authenticated user — otherwise <code class="language-plaintext highlighter-rouge">404 Not Found</code></li>
</ul>]]></content><author><name></name></author><category term="api-reference" /><category term="satisfactory" /><category term="ficsitmonitor" /><category term="api" /><category term="metrics-api" /><category term="monitoring" /><category term="rest-api" /><summary type="html"><![CDATA[Query Satisfactory server metrics via the FICSIT.monitor API. All /v1/servers/{id}/ endpoints with response field definitions and example responses.]]></summary></entry><entry><title type="html">Servers API</title><link href="https://apptolast.github.io/FicsitDocumentation/posts/api-servers/" rel="alternate" type="text/html" title="Servers API" /><published>2026-04-15T02:22:00+02:00</published><updated>2026-04-15T02:22:00+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/posts/api-servers</id><content type="html" xml:base="https://apptolast.github.io/FicsitDocumentation/posts/api-servers/"><![CDATA[<h2 id="overview">Overview</h2>

<p>The Servers API provides CRUD operations for managing your Satisfactory server
connections in FICSIT.monitor. All endpoints require authentication.</p>

<p><strong>Base path:</strong> <code class="language-plaintext highlighter-rouge">/api/v1/servers</code></p>

<hr />

<h2 id="server-object">Server Object</h2>

<p>The API returns servers in this format:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"uuid"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Factory Server"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"host"</span><span class="p">:</span><span class="w"> </span><span class="s2">"46.224.182.211"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"api_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">7777</span><span class="p">,</span><span class="w">
  </span><span class="nl">"frm_http_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">8080</span><span class="p">,</span><span class="w">
  </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"online"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"last_seen_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-01T00:00:00Z"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Field</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">id</code></td>
      <td>UUID — use this to reference the server in metric endpoints</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">name</code></td>
      <td>Display name</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">host</code></td>
      <td>IP address of the server</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">api_port</code></td>
      <td>Vanilla API port (usually 7777)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">frm_http_port</code></td>
      <td>FRM HTTP API port (usually 8080)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">status</code></td>
      <td><code class="language-plaintext highlighter-rouge">online</code>, <code class="language-plaintext highlighter-rouge">offline</code>, or <code class="language-plaintext highlighter-rouge">degraded</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">last_seen_at</code></td>
      <td>Last time the server responded to a health check</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">created_at</code></td>
      <td>When this server was added to FICSIT.monitor</td>
    </tr>
  </tbody>
</table>

<blockquote class="prompt-info">
  <p><code class="language-plaintext highlighter-rouge">frm_ws_port</code> and <code class="language-plaintext highlighter-rouge">api_token</code> are not included in the response.</p>
</blockquote>

<hr />

<h2 id="get-apiv1servers">GET /api/v1/servers</h2>

<p>List all servers belonging to the authenticated user.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Response (200):</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"550e8400-e29b-41d4-a716-446655440000"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Production Server"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"host"</span><span class="p">:</span><span class="w"> </span><span class="s2">"46.224.182.211"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"api_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">7777</span><span class="p">,</span><span class="w">
    </span><span class="nl">"frm_http_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">8080</span><span class="p">,</span><span class="w">
    </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"online"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"last_seen_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-01T00:00:00Z"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="post-apiv1servers">POST /api/v1/servers</h2>

<p>Add a new Satisfactory server to FICSIT.monitor. This triggers the onboarding
sequence: connectivity check, authentication, and initial metrics fetch.</p>

<p><strong>Request body:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Factory Server"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"host"</span><span class="p">:</span><span class="w"> </span><span class="s2">"YOUR_SERVER_IP"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"api_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">7777</span><span class="p">,</span><span class="w">
  </span><span class="nl">"frm_http_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">8080</span><span class="p">,</span><span class="w">
  </span><span class="nl">"frm_ws_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">8081</span><span class="p">,</span><span class="w">
  </span><span class="nl">"admin_password"</span><span class="p">:</span><span class="w"> </span><span class="s2">"YourAdminPassword"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Field</th>
      <th>Required</th>
      <th>Default</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">name</code></td>
      <td>Yes</td>
      <td>—</td>
      <td>Display name</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">host</code></td>
      <td>Yes</td>
      <td>—</td>
      <td>Public IP of the server</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">api_port</code></td>
      <td>No</td>
      <td>7777</td>
      <td>Vanilla HTTPS API port</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">frm_http_port</code></td>
      <td>No</td>
      <td>8080</td>
      <td>FRM HTTP port</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">frm_ws_port</code></td>
      <td>No</td>
      <td>8081</td>
      <td>FRM WebSocket port</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">admin_password</code></td>
      <td>Yes</td>
      <td>—</td>
      <td>Server admin password</td>
    </tr>
  </tbody>
</table>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-X</span> POST https://satisfactory-dashboard.pablohgdev.com/api/v1/servers <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "name": "My Factory Server",
    "host": "YOUR_SERVER_IP",
    "api_port": 7777,
    "frm_http_port": 8080,
    "frm_ws_port": 8081,
    "admin_password": "YourAdminPassword"
  }'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Success response (201):</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"550e8400-e29b-41d4-a716-446655440000"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Factory Server"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"host"</span><span class="p">:</span><span class="w"> </span><span class="s2">"YOUR_SERVER_IP"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"api_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">7777</span><span class="p">,</span><span class="w">
  </span><span class="nl">"frm_http_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">8080</span><span class="p">,</span><span class="w">
  </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"online"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"last_seen_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="onboarding-error-codes">Onboarding Error Codes</h3>

<table>
  <thead>
    <tr>
      <th>HTTP Status</th>
      <th>Cause</th>
      <th>Fix</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>422</strong></td>
      <td><code class="language-plaintext highlighter-rouge">InvalidAdminPasswordException</code> — wrong admin password</td>
      <td>Verify the admin password set in Server Settings → Administration</td>
    </tr>
    <tr>
      <td><strong>502</strong></td>
      <td><code class="language-plaintext highlighter-rouge">ServerUnreachableException</code> — cannot reach host:port</td>
      <td>Check firewall (port 7777), verify server is running</td>
    </tr>
    <tr>
      <td><strong>500</strong></td>
      <td><code class="language-plaintext highlighter-rouge">ProvisioningFailedException</code> — setup failed after connecting</td>
      <td>Usually FRM not accessible (port 8080 blocked or FRM not installed)</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="get-apiv1serversid">GET /api/v1/servers/{id}</h2>

<p>Get details for a specific server.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/SERVER_UUID <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Response (200):</strong> Single server object (same format as the list).</p>

<p><strong>Not found (404):</strong> Server does not exist or belongs to another user.</p>

<hr />

<h2 id="put-apiv1serversid">PUT /api/v1/servers/{id}</h2>

<p>Update server settings. Only the fields you provide are updated.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-X</span> PUT https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/SERVER_UUID <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "name": "Updated Server Name",
    "admin_password": "NewPassword"
  }'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Response (200):</strong> Updated server object.</p>

<hr />

<h2 id="delete-apiv1serversid">DELETE /api/v1/servers/{id}</h2>

<p>Remove a server from FICSIT.monitor. This stops polling for the server and removes
it from your dashboard. Historical metric data is retained for your plan’s retention
period.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-X</span> DELETE https://satisfactory-dashboard.pablohgdev.com/api/v1/servers/SERVER_UUID <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Response (204):</strong> Empty body.</p>

<hr />

<h2 id="finding-your-server-uuid">Finding Your Server UUID</h2>

<p>Your server’s UUID is returned when you add it and in the <code class="language-plaintext highlighter-rouge">GET /api/v1/servers</code> list.
It is also visible in the dashboard URL: <code class="language-plaintext highlighter-rouge">/servers/{uuid}/dashboard</code>.</p>]]></content><author><name></name></author><category term="api-reference" /><category term="satisfactory" /><category term="ficsitmonitor" /><category term="api" /><category term="servers-api" /><category term="onboarding" /><summary type="html"><![CDATA[Manage Satisfactory servers via the FICSIT.monitor API. List, create, update, and delete server connections. Onboarding request fields and error codes.]]></summary></entry><entry><title type="html">API Authentication</title><link href="https://apptolast.github.io/FicsitDocumentation/posts/api-authentication/" rel="alternate" type="text/html" title="API Authentication" /><published>2026-04-15T02:21:00+02:00</published><updated>2026-04-16T01:01:21+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/posts/api-authentication</id><content type="html" xml:base="https://apptolast.github.io/FicsitDocumentation/posts/api-authentication/"><![CDATA[<h2 id="overview">Overview</h2>

<p>FICSIT.monitor uses <strong>Laravel Sanctum</strong> for API authentication. The API supports
session-based authentication (cookie) for the web SPA and token-based authentication
for external API clients.</p>

<hr />

<h2 id="authentication-endpoints">Authentication Endpoints</h2>

<h3 id="post-apiregister">POST /api/register</h3>

<p>Create a new user account.</p>

<p><strong>Request:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-X</span> POST https://satisfactory-dashboard.pablohgdev.com/api/register <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "name": "Pablo",
    "email": "pablo@example.com",
    "password": "your_secure_password",
    "password_confirmation": "your_secure_password"
  }'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Success response (201):</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"uuid"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Pablo"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pablo@example.com"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Validation error (422):</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The given data was invalid."</span><span class="p">,</span><span class="w">
  </span><span class="nl">"errors"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"The email has already been taken."</span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h3 id="post-apilogin">POST /api/login</h3>

<p>Authenticate with email and password. Returns a session cookie.</p>

<p><strong>Request:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-X</span> POST https://satisfactory-dashboard.pablohgdev.com/api/login <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="se">\</span>
  <span class="nt">-c</span> cookies.txt <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "email": "pablo@example.com",
    "password": "your_secure_password"
  }'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Success response (200):</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"uuid"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Pablo"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pablo@example.com"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>The session cookie is stored in <code class="language-plaintext highlighter-rouge">cookies.txt</code> with <code class="language-plaintext highlighter-rouge">-c cookies.txt</code>. Use <code class="language-plaintext highlighter-rouge">-b cookies.txt</code>
in subsequent requests to authenticate.</p>

<p><strong>Authentication failure (401):</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"These credentials do not match our records."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h3 id="post-apilogout">POST /api/logout</h3>

<p>Invalidate the current session. Requires authentication.</p>

<p><strong>Request:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-X</span> POST https://satisfactory-dashboard.pablohgdev.com/api/logout <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="se">\</span>
  <span class="nt">-b</span> cookies.txt
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Success response (204):</strong> Empty body.</p>

<hr />

<h3 id="get-apiuser">GET /api/user</h3>

<p>Get the currently authenticated user. Requires authentication.</p>

<p><strong>Request:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/user <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="se">\</span>
  <span class="nt">-b</span> cookies.txt
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Success response (200):</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"uuid"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Pablo"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pablo@example.com"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="using-the-session-for-api-requests">Using the Session for API Requests</h2>

<p>After login, include the session cookie in all authenticated requests:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="c"># Login and save cookie</span>
curl <span class="nt">-X</span> POST https://satisfactory-dashboard.pablohgdev.com/api/login <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="se">\</span>
  <span class="nt">-c</span> cookies.txt <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"email":"pablo@example.com","password":"your_password"}'</span>

<span class="c"># Use the session for subsequent requests</span>
curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="se">\</span>
  <span class="nt">-b</span> cookies.txt
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="csrf-protection">CSRF Protection</h2>

<p>For browser-based clients making state-changing requests (POST, PUT, DELETE), a CSRF
token is required. The SPA dashboard handles this automatically. External API clients
using session cookies should fetch the CSRF token from <code class="language-plaintext highlighter-rouge">/sanctum/csrf-cookie</code> before
making state-changing requests.</p>

<p>For scripts and tools, using session cookies with the <code class="language-plaintext highlighter-rouge">Accept: application/json</code> header
bypasses CSRF requirements on JSON API endpoints.</p>

<hr />

<h2 id="unauthenticated-response">Unauthenticated Response</h2>

<p>Any request to a protected endpoint without authentication returns:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Unauthenticated."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>HTTP status: <code class="language-plaintext highlighter-rouge">401 Unauthorized</code></p>

<hr />

<h2 id="see-also">See Also</h2>

<ul>
  <li><a href="/FicsitDocumentation/posts/api-overview/">REST API Overview</a> — base URL and general usage</li>
  <li><a href="/FicsitDocumentation/posts/api-servers/">Servers API</a> — manage your servers</li>
  <li><a href="/FicsitDocumentation/posts/api-metrics/">Metrics API</a> — query server metrics</li>
</ul>]]></content><author><name></name></author><category term="api-reference" /><category term="satisfactory" /><category term="ficsitmonitor" /><category term="api" /><category term="authentication" /><category term="sanctum" /><category term="tokens" /><summary type="html"><![CDATA[Authenticate with the FICSIT.monitor API using Sanctum session authentication. Register, login, logout, and retrieve the current user via the REST API.]]></summary></entry><entry><title type="html">REST API Overview</title><link href="https://apptolast.github.io/FicsitDocumentation/posts/api-overview/" rel="alternate" type="text/html" title="REST API Overview" /><published>2026-04-15T02:20:00+02:00</published><updated>2026-04-16T01:01:21+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/posts/api-overview</id><content type="html" xml:base="https://apptolast.github.io/FicsitDocumentation/posts/api-overview/"><![CDATA[<h2 id="overview">Overview</h2>

<p>FICSIT.monitor exposes a REST API for programmatic access to server metrics, player
data, and server management. The API uses Sanctum for authentication (Personal Access
Tokens or session cookies).</p>

<hr />

<h2 id="base-url">Base URL</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>https://satisfactory-dashboard.pablohgdev.com/api
</pre></td></tr></tbody></table></code></pre></div></div>

<p>All endpoints described in this documentation are relative to this base URL.</p>

<hr />

<h2 id="authentication">Authentication</h2>

<p>The API supports two authentication methods:</p>

<h3 id="personal-access-tokens-pats">Personal Access Tokens (PATs)</h3>

<p>Include the token in the <code class="language-plaintext highlighter-rouge">Authorization</code> header:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>curl https://satisfactory-dashboard.pablohgdev.com/api/v1/servers <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer YOUR_TOKEN"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="session-cookie-spa">Session Cookie (SPA)</h3>

<p>Used by the web dashboard. The cookie is set when you log in via <code class="language-plaintext highlighter-rouge">POST /api/login</code>.
Not suitable for external API clients.</p>

<hr />

<h2 id="content-type">Content Type</h2>

<p>All requests with a body must include:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>Content-Type: application/json
</pre></td></tr></tbody></table></code></pre></div></div>

<p>All responses are JSON with <code class="language-plaintext highlighter-rouge">Content-Type: application/json</code>.</p>

<hr />

<h2 id="rate-limiting">Rate Limiting</h2>

<table>
  <thead>
    <tr>
      <th>Endpoint group</th>
      <th>Limit</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Authentication (<code class="language-plaintext highlighter-rouge">/register</code>, <code class="language-plaintext highlighter-rouge">/login</code>)</td>
      <td>6 requests/minute</td>
    </tr>
    <tr>
      <td>All other endpoints</td>
      <td>No hard limit (subject to fair use)</td>
    </tr>
  </tbody>
</table>

<p>Rate limit responses return HTTP <code class="language-plaintext highlighter-rouge">429 Too Many Requests</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Too Many Attempts."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="response-format">Response Format</h2>

<h3 id="success">Success</h3>

<p>Successful responses return the resource directly:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"uuid"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Server"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"host"</span><span class="p">:</span><span class="w"> </span><span class="s2">"46.224.182.211"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"api_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">7777</span><span class="p">,</span><span class="w">
  </span><span class="nl">"frm_http_port"</span><span class="p">:</span><span class="w"> </span><span class="mi">8080</span><span class="p">,</span><span class="w">
  </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"online"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"last_seen_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-15T12:00:00Z"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-04-01T00:00:00Z"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>List endpoints return arrays (pagination may apply).</p>

<h3 id="errors">Errors</h3>

<p>Errors return a standard format:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Human-readable error message"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"errors"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"field_name"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Specific validation error"</span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="http-status-codes">HTTP Status Codes</h2>

<table>
  <thead>
    <tr>
      <th>Code</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">200 OK</code></td>
      <td>Request succeeded</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">201 Created</code></td>
      <td>Resource created successfully</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">204 No Content</code></td>
      <td>Deletion succeeded</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">401 Unauthorized</code></td>
      <td>Not authenticated</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">403 Forbidden</code></td>
      <td>Authenticated but not authorized for this resource</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">404 Not Found</code></td>
      <td>Resource does not exist</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">422 Unprocessable Entity</code></td>
      <td>Validation failed (or wrong admin password)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">429 Too Many Requests</code></td>
      <td>Rate limit exceeded</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">500 Internal Server Error</code></td>
      <td>Server-side error (check logs)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">502 Bad Gateway</code></td>
      <td>FICSIT.monitor cannot reach your Satisfactory server</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="endpoint-overview">Endpoint Overview</h2>

<h3 id="authentication-1">Authentication</h3>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>Path</th>
      <th>Auth</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>POST</td>
      <td><code class="language-plaintext highlighter-rouge">/register</code></td>
      <td>No</td>
      <td>Create a new user account</td>
    </tr>
    <tr>
      <td>POST</td>
      <td><code class="language-plaintext highlighter-rouge">/login</code></td>
      <td>No</td>
      <td>Authenticate and get session</td>
    </tr>
    <tr>
      <td>POST</td>
      <td><code class="language-plaintext highlighter-rouge">/logout</code></td>
      <td>Yes</td>
      <td>Invalidate session</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/user</code></td>
      <td>Yes</td>
      <td>Get current user</td>
    </tr>
  </tbody>
</table>

<h3 id="configuration">Configuration</h3>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>Path</th>
      <th>Auth</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/config/reverb</code></td>
      <td>Yes</td>
      <td>WebSocket connection config for the frontend</td>
    </tr>
  </tbody>
</table>

<h3 id="servers">Servers</h3>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>Path</th>
      <th>Auth</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers</code></td>
      <td>Yes</td>
      <td>List all user’s servers</td>
    </tr>
    <tr>
      <td>POST</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers</code></td>
      <td>Yes</td>
      <td>Add a new server</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}</code></td>
      <td>Yes</td>
      <td>Get server details</td>
    </tr>
    <tr>
      <td>PUT/PATCH</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}</code></td>
      <td>Yes</td>
      <td>Update server</td>
    </tr>
    <tr>
      <td>DELETE</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}</code></td>
      <td>Yes</td>
      <td>Remove server</td>
    </tr>
  </tbody>
</table>

<h3 id="server-metrics">Server Metrics</h3>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>Path</th>
      <th>Auth</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/metrics/latest</code></td>
      <td>Yes</td>
      <td>Latest server state</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/players</code></td>
      <td>Yes</td>
      <td>Player list</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/power/latest</code></td>
      <td>Yes</td>
      <td>Latest power metrics</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/production/latest</code></td>
      <td>Yes</td>
      <td>Latest production metrics</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/trains</code></td>
      <td>Yes</td>
      <td>Train list</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/drones</code></td>
      <td>Yes</td>
      <td>Drone station list</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/generators</code></td>
      <td>Yes</td>
      <td>Generator list</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/extractors</code></td>
      <td>Yes</td>
      <td>Extractor list</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/world-inventory</code></td>
      <td>Yes</td>
      <td>World inventory</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/resource-sink</code></td>
      <td>Yes</td>
      <td>Resource sink data</td>
    </tr>
    <tr>
      <td>GET</td>
      <td><code class="language-plaintext highlighter-rouge">/v1/servers/{id}/dashboard</code></td>
      <td>Yes</td>
      <td>Full metrics snapshot</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="cors">CORS</h2>

<p>The API allows requests from the dashboard’s own domain. For external API clients,
requests must include the <code class="language-plaintext highlighter-rouge">Accept: application/json</code> header to receive JSON error
responses instead of HTML redirects.</p>

<hr />

<h2 id="see-also">See Also</h2>

<ul>
  <li><a href="/FicsitDocumentation/posts/api-authentication/">API Authentication</a> — PAT creation and usage</li>
  <li><a href="/FicsitDocumentation/posts/api-servers/">Servers API</a> — server management endpoints</li>
  <li><a href="/FicsitDocumentation/posts/api-metrics/">Metrics API</a> — all metric endpoints with field definitions</li>
</ul>]]></content><author><name></name></author><category term="api-reference" /><category term="satisfactory" /><category term="ficsitmonitor" /><category term="api" /><category term="rest-api" /><category term="sanctum" /><category term="authentication" /><summary type="html"><![CDATA[FICSIT.monitor REST API overview. Base URL, Sanctum authentication, rate limits, JSON response format, and common error codes.]]></summary></entry><entry><title type="html">Environment Variables Reference</title><link href="https://apptolast.github.io/FicsitDocumentation/posts/environment-variables/" rel="alternate" type="text/html" title="Environment Variables Reference" /><published>2026-04-15T02:19:00+02:00</published><updated>2026-04-15T02:19:00+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/posts/environment-variables</id><content type="html" xml:base="https://apptolast.github.io/FicsitDocumentation/posts/environment-variables/"><![CDATA[<h2 id="overview">Overview</h2>

<p>FICSIT.monitor is a Laravel 12 application. All configuration is done through
environment variables, either in a <code class="language-plaintext highlighter-rouge">.env</code> file for local development or Kubernetes
Secrets/ConfigMaps for production.</p>

<blockquote class="prompt-danger">
  <p>Never commit your <code class="language-plaintext highlighter-rouge">.env</code> file or Kubernetes Secrets to a public repository.
The <code class="language-plaintext highlighter-rouge">.env.example</code> file provides a safe template with no real values.</p>
</blockquote>

<hr />

<h2 id="application-settings">Application Settings</h2>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Example</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">APP_NAME</code></td>
      <td><code class="language-plaintext highlighter-rouge">Satisfactory Dashboard</code></td>
      <td>Application display name</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">APP_ENV</code></td>
      <td><code class="language-plaintext highlighter-rouge">production</code></td>
      <td>Environment: <code class="language-plaintext highlighter-rouge">local</code>, <code class="language-plaintext highlighter-rouge">production</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">APP_KEY</code></td>
      <td><code class="language-plaintext highlighter-rouge">base64:xxx...</code></td>
      <td>Laravel application encryption key. Generate with <code class="language-plaintext highlighter-rouge">php artisan key:generate</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">APP_DEBUG</code></td>
      <td><code class="language-plaintext highlighter-rouge">false</code></td>
      <td>Enable debug mode. <strong>Set to <code class="language-plaintext highlighter-rouge">false</code> in production</strong></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">APP_URL</code></td>
      <td><code class="language-plaintext highlighter-rouge">https://your-domain.com</code></td>
      <td>Public URL of the application</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">APP_LOCALE</code></td>
      <td><code class="language-plaintext highlighter-rouge">en</code></td>
      <td>Application locale</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="database-postgresql--timescaledb">Database (PostgreSQL + TimescaleDB)</h2>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Example</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DB_CONNECTION</code></td>
      <td><code class="language-plaintext highlighter-rouge">pgsql</code></td>
      <td>Database driver — <strong>must be <code class="language-plaintext highlighter-rouge">pgsql</code></strong> (PostgreSQL)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DB_HOST</code></td>
      <td><code class="language-plaintext highlighter-rouge">postgres</code></td>
      <td>Database hostname (K8s service name or IP)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DB_PORT</code></td>
      <td><code class="language-plaintext highlighter-rouge">5432</code></td>
      <td>PostgreSQL port</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DB_DATABASE</code></td>
      <td><code class="language-plaintext highlighter-rouge">satisfactory_dashboard</code></td>
      <td>Database name</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DB_USERNAME</code></td>
      <td><code class="language-plaintext highlighter-rouge">satisfactory</code></td>
      <td>Database user</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DB_PASSWORD</code></td>
      <td><code class="language-plaintext highlighter-rouge">strong_password</code></td>
      <td>Database password</td>
    </tr>
  </tbody>
</table>

<blockquote class="prompt-warning">
  <p>TimescaleDB must be installed and enabled in PostgreSQL. The migrations run
<code class="language-plaintext highlighter-rouge">CREATE EXTENSION timescaledb;</code> and create hypertables for time-series data.</p>
</blockquote>

<hr />

<h2 id="redis">Redis</h2>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Example</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REDIS_CLIENT</code></td>
      <td><code class="language-plaintext highlighter-rouge">phpredis</code></td>
      <td>PHP Redis client. Use <code class="language-plaintext highlighter-rouge">phpredis</code> for best performance</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REDIS_HOST</code></td>
      <td><code class="language-plaintext highlighter-rouge">redis</code></td>
      <td>Redis hostname (K8s service name or IP)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REDIS_PASSWORD</code></td>
      <td><code class="language-plaintext highlighter-rouge">null</code></td>
      <td>Redis password (null if no auth configured)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REDIS_PORT</code></td>
      <td><code class="language-plaintext highlighter-rouge">6379</code></td>
      <td>Redis port</td>
    </tr>
  </tbody>
</table>

<p>Redis is used for: queue backing (Horizon), session storage, and current-state metric cache.</p>

<hr />

<h2 id="session--queue">Session &amp; Queue</h2>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Value</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SESSION_DRIVER</code></td>
      <td><code class="language-plaintext highlighter-rouge">redis</code></td>
      <td><strong>Must be <code class="language-plaintext highlighter-rouge">redis</code></strong> for WebSocket authentication to work</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SESSION_LIFETIME</code></td>
      <td><code class="language-plaintext highlighter-rouge">120</code></td>
      <td>Session lifetime in minutes</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">QUEUE_CONNECTION</code></td>
      <td><code class="language-plaintext highlighter-rouge">redis</code></td>
      <td><strong>Must be <code class="language-plaintext highlighter-rouge">redis</code></strong> — uses Laravel Horizon</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">CACHE_STORE</code></td>
      <td><code class="language-plaintext highlighter-rouge">redis</code></td>
      <td>Cache backend</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">BROADCAST_CONNECTION</code></td>
      <td><code class="language-plaintext highlighter-rouge">reverb</code></td>
      <td><strong>Must be <code class="language-plaintext highlighter-rouge">reverb</code></strong> — uses Laravel Reverb WebSocket</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="websocket-laravel-reverb">WebSocket (Laravel Reverb)</h2>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Example</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REVERB_APP_ID</code></td>
      <td><code class="language-plaintext highlighter-rouge">satisfactory-dashboard</code></td>
      <td>Reverb application ID</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REVERB_APP_KEY</code></td>
      <td><code class="language-plaintext highlighter-rouge">your-reverb-key</code></td>
      <td>Reverb application key (also used by frontend)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REVERB_APP_SECRET</code></td>
      <td><code class="language-plaintext highlighter-rouge">your-reverb-secret</code></td>
      <td>Reverb application secret</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REVERB_HOST</code></td>
      <td><code class="language-plaintext highlighter-rouge">reverb-service</code></td>
      <td>Hostname of the Reverb WebSocket server</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REVERB_PORT</code></td>
      <td><code class="language-plaintext highlighter-rouge">8080</code></td>
      <td>Internal Reverb port</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">REVERB_SCHEME</code></td>
      <td><code class="language-plaintext highlighter-rouge">http</code></td>
      <td>Internal scheme (<code class="language-plaintext highlighter-rouge">http</code> inside K8s, <code class="language-plaintext highlighter-rouge">https</code> if behind proxy)</td>
    </tr>
  </tbody>
</table>

<p><strong>Frontend variables</strong> (used by Vite to build the React frontend):</p>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Value</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">VITE_APP_NAME</code></td>
      <td><code class="language-plaintext highlighter-rouge">${APP_NAME}</code></td>
      <td>App name for frontend</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">VITE_REVERB_APP_KEY</code></td>
      <td><code class="language-plaintext highlighter-rouge">${REVERB_APP_KEY}</code></td>
      <td>Reverb key for Laravel Echo</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">VITE_REVERB_HOST</code></td>
      <td><code class="language-plaintext highlighter-rouge">your-domain.com</code></td>
      <td>Public hostname for WebSocket connections</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">VITE_REVERB_PORT</code></td>
      <td><code class="language-plaintext highlighter-rouge">443</code></td>
      <td>Public WebSocket port (443 if using HTTPS/WSS)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">VITE_REVERB_SCHEME</code></td>
      <td><code class="language-plaintext highlighter-rouge">https</code></td>
      <td>Public WebSocket scheme</td>
    </tr>
  </tbody>
</table>

<blockquote class="prompt-warning">
  <p><code class="language-plaintext highlighter-rouge">VITE_REVERB_HOST</code> should be your public domain, not the internal service name.
Clients connect to it from their browsers.</p>
</blockquote>

<hr />

<h2 id="satisfactory-server-connection">Satisfactory Server Connection</h2>

<p>These variables configure how FICSIT.monitor connects to your Satisfactory server.
In production, these are set dynamically per-server via the database. The env vars
below are for a single default server in development/testing scenarios.</p>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Example</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SATISFACTORY_SERVER_HOST</code></td>
      <td><code class="language-plaintext highlighter-rouge">46.224.182.211</code></td>
      <td>IP address of the Satisfactory server</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SATISFACTORY_API_PORT</code></td>
      <td><code class="language-plaintext highlighter-rouge">7777</code></td>
      <td>Satisfactory vanilla HTTPS API port</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SATISFACTORY_API_TOKEN</code></td>
      <td>(empty)</td>
      <td>Pre-configured API token (optional; FICSIT.monitor generates tokens via PasswordLogin)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SATISFACTORY_FRM_HOST</code></td>
      <td><code class="language-plaintext highlighter-rouge">46.224.182.211</code></td>
      <td>IP address for FRM access (usually same as server host)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SATISFACTORY_FRM_PORT</code></td>
      <td><code class="language-plaintext highlighter-rouge">8080</code></td>
      <td>FRM HTTP API port</td>
    </tr>
  </tbody>
</table>

<blockquote class="prompt-info">
  <p>In Kubernetes, <code class="language-plaintext highlighter-rouge">SATISFACTORY_FRM_HOST</code> can be the internal ClusterIP service name
<code class="language-plaintext highlighter-rouge">satisfactory-frm.satisfactory.svc.cluster.local</code> when the dashboard and game server
are in the same cluster, avoiding external network traffic.</p>
</blockquote>

<hr />

<h2 id="mail-optional">Mail (Optional)</h2>

<p>Mail is used for account-related emails. In development, <code class="language-plaintext highlighter-rouge">MAIL_MAILER=log</code> writes
emails to the application log instead of sending them.</p>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Value</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MAIL_MAILER</code></td>
      <td><code class="language-plaintext highlighter-rouge">log</code> (dev) / <code class="language-plaintext highlighter-rouge">smtp</code> (prod)</td>
      <td>Mail driver</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MAIL_HOST</code></td>
      <td><code class="language-plaintext highlighter-rouge">smtp.example.com</code></td>
      <td>SMTP server hostname</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MAIL_PORT</code></td>
      <td><code class="language-plaintext highlighter-rouge">587</code></td>
      <td>SMTP port</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MAIL_USERNAME</code></td>
      <td><code class="language-plaintext highlighter-rouge">user@example.com</code></td>
      <td>SMTP username</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MAIL_PASSWORD</code></td>
      <td><code class="language-plaintext highlighter-rouge">password</code></td>
      <td>SMTP password</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MAIL_FROM_ADDRESS</code></td>
      <td><code class="language-plaintext highlighter-rouge">no-reply@yourdomain.com</code></td>
      <td>Sender email address</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MAIL_FROM_NAME</code></td>
      <td><code class="language-plaintext highlighter-rouge">FICSIT.monitor</code></td>
      <td>Sender display name</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="minimum-production-env">Minimum Production <code class="language-plaintext highlighter-rouge">.env</code></h2>

<p>A minimal working production configuration:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
</pre></td><td class="rouge-code"><pre><span class="nv">APP_NAME</span><span class="o">=</span><span class="s2">"FICSIT.monitor"</span>
<span class="nv">APP_ENV</span><span class="o">=</span>production
<span class="nv">APP_KEY</span><span class="o">=</span><span class="nb">base64</span>:GENERATE_THIS_WITH_ARTISAN
<span class="nv">APP_DEBUG</span><span class="o">=</span><span class="nb">false
</span><span class="nv">APP_URL</span><span class="o">=</span>https://your-domain.com

<span class="nv">DB_CONNECTION</span><span class="o">=</span>pgsql
<span class="nv">DB_HOST</span><span class="o">=</span>postgres
<span class="nv">DB_PORT</span><span class="o">=</span>5432
<span class="nv">DB_DATABASE</span><span class="o">=</span>satisfactory_dashboard
<span class="nv">DB_USERNAME</span><span class="o">=</span>satisfactory
<span class="nv">DB_PASSWORD</span><span class="o">=</span>STRONG_PASSWORD

<span class="nv">REDIS_CLIENT</span><span class="o">=</span>phpredis
<span class="nv">REDIS_HOST</span><span class="o">=</span>redis
<span class="nv">REDIS_PORT</span><span class="o">=</span>6379

<span class="nv">SESSION_DRIVER</span><span class="o">=</span>redis
<span class="nv">QUEUE_CONNECTION</span><span class="o">=</span>redis
<span class="nv">BROADCAST_CONNECTION</span><span class="o">=</span>reverb
<span class="nv">CACHE_STORE</span><span class="o">=</span>redis

<span class="nv">REVERB_APP_ID</span><span class="o">=</span>satisfactory-dashboard
<span class="nv">REVERB_APP_KEY</span><span class="o">=</span>YOUR_REVERB_KEY
<span class="nv">REVERB_APP_SECRET</span><span class="o">=</span>YOUR_REVERB_SECRET
<span class="nv">REVERB_HOST</span><span class="o">=</span>reverb
<span class="nv">REVERB_PORT</span><span class="o">=</span>8080
<span class="nv">REVERB_SCHEME</span><span class="o">=</span>http

<span class="nv">VITE_REVERB_APP_KEY</span><span class="o">=</span>YOUR_REVERB_KEY
<span class="nv">VITE_REVERB_HOST</span><span class="o">=</span>your-domain.com
<span class="nv">VITE_REVERB_PORT</span><span class="o">=</span>443
<span class="nv">VITE_REVERB_SCHEME</span><span class="o">=</span>https
</pre></td></tr></tbody></table></code></pre></div></div>]]></content><author><name></name></author><category term="deployment" /><category term="satisfactory" /><category term="ficsitmonitor" /><category term="environment-variables" /><category term="configuration" /><category term="docker" /><category term="kubernetes" /><summary type="html"><![CDATA[Complete reference for all FICSIT.monitor environment variables. Database, Redis, Reverb WebSocket, Satisfactory server connection, and application settings.]]></summary></entry><entry><title type="html">Kubernetes Deployment Guide</title><link href="https://apptolast.github.io/FicsitDocumentation/posts/kubernetes-deployment/" rel="alternate" type="text/html" title="Kubernetes Deployment Guide" /><published>2026-04-15T02:18:00+02:00</published><updated>2026-04-16T01:01:21+02:00</updated><id>https://apptolast.github.io/FicsitDocumentation/posts/kubernetes-deployment</id><content type="html" xml:base="https://apptolast.github.io/FicsitDocumentation/posts/kubernetes-deployment/"><![CDATA[<h2 id="overview">Overview</h2>

<p>This guide describes the production Kubernetes deployment powering FICSIT.monitor.
It deploys two namespaces: <code class="language-plaintext highlighter-rouge">satisfactory</code> for the game server, and
<code class="language-plaintext highlighter-rouge">satisfactory-dashboard</code> for the monitoring application stack.</p>

<p>This is an <strong>advanced guide</strong> targeting teams with Kubernetes experience. For a simpler
setup, use the <a href="/FicsitDocumentation/posts/run-satisfactory-docker/">Docker deployment guide</a>.</p>

<hr />

<h2 id="prerequisites">Prerequisites</h2>

<p>A bare-metal or cloud Kubernetes cluster with:</p>
<ul>
  <li><strong>MetalLB</strong> — bare-metal load balancer (for assigning external IPs)</li>
  <li><strong>Longhorn</strong> — distributed block storage (for persistent volumes)</li>
  <li><strong>Traefik</strong> — ingress controller</li>
  <li><strong>cert-manager</strong> — TLS certificate management (Let’s Encrypt)</li>
  <li><strong>Keel</strong> — automated image update deployment</li>
</ul>

<hr />

<h2 id="namespace-structure">Namespace Structure</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="c1"># satisfactory namespace — game server</span>
<span class="c1"># satisfactory-dashboard namespace — monitoring app</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="game-server-deployment-namespace-satisfactory">Game Server Deployment (namespace: <code class="language-plaintext highlighter-rouge">satisfactory</code>)</h2>

<h3 id="statefulset">StatefulSet</h3>

<p>The game server runs as a StatefulSet with 1 replica:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">StatefulSet</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">satisfactory</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">satisfactory</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">keel.sh/policy</span><span class="pi">:</span> <span class="s">force</span>
    <span class="na">keel.sh/trigger</span><span class="pi">:</span> <span class="s">poll</span>
    <span class="na">keel.sh/pollSchedule</span><span class="pi">:</span> <span class="s2">"</span><span class="s">@every</span><span class="nv"> </span><span class="s">6h"</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">serviceName</span><span class="pi">:</span> <span class="s">satisfactory</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">containers</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">satisfactory</span>
          <span class="na">image</span><span class="pi">:</span> <span class="s">wolveix/satisfactory-server:latest</span>
          <span class="na">env</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">MAXPLAYERS</span>
              <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">8"</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">PGID</span>
              <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">1000"</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">PUID</span>
              <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">1000"</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">STEAMBETA</span>
              <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">false"</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">SKIPUPDATE</span>
              <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">false"</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">AUTOSAVEINTERVAL</span>
              <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">300"</span>
          <span class="na">ports</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">game-tcp</span>
              <span class="na">containerPort</span><span class="pi">:</span> <span class="m">7777</span>
              <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">game-udp</span>
              <span class="na">containerPort</span><span class="pi">:</span> <span class="m">7777</span>
              <span class="na">protocol</span><span class="pi">:</span> <span class="s">UDP</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">reliable</span>
              <span class="na">containerPort</span><span class="pi">:</span> <span class="m">8888</span>
              <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">frm-http</span>
              <span class="na">containerPort</span><span class="pi">:</span> <span class="m">8080</span>
              <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">frm-ws</span>
              <span class="na">containerPort</span><span class="pi">:</span> <span class="m">8081</span>
              <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
          <span class="na">resources</span><span class="pi">:</span>
            <span class="na">requests</span><span class="pi">:</span>
              <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2"</span>
              <span class="na">memory</span><span class="pi">:</span> <span class="s">8Gi</span>
            <span class="na">limits</span><span class="pi">:</span>
              <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">4"</span>
              <span class="na">memory</span><span class="pi">:</span> <span class="s">16Gi</span>
          <span class="na">volumeMounts</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">gamedata</span>
              <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/config</span>
  <span class="na">volumeClaimTemplates</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">metadata</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">gamedata</span>
      <span class="na">spec</span><span class="pi">:</span>
        <span class="na">accessModes</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s">ReadWriteOnce</span>
        <span class="na">storageClassName</span><span class="pi">:</span> <span class="s">longhorn-gameserver</span>
        <span class="na">resources</span><span class="pi">:</span>
          <span class="na">requests</span><span class="pi">:</span>
            <span class="na">storage</span><span class="pi">:</span> <span class="s">75Gi</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="services">Services</h3>

<p><strong>satisfactory-tcp</strong> (LoadBalancer — external game + API access):</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">satisfactory-tcp</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">satisfactory</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">metallb.universe.tf/allow-shared-ip</span><span class="pi">:</span> <span class="s2">"</span><span class="s">shared-external-ip"</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">LoadBalancer</span>
  <span class="na">loadBalancerIP</span><span class="pi">:</span> <span class="s2">"</span><span class="s">YOUR_PUBLIC_IP"</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">app.kubernetes.io/name</span><span class="pi">:</span> <span class="s">satisfactory</span>
  <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">game-tcp</span>
      <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">7777</span>
      <span class="na">targetPort</span><span class="pi">:</span> <span class="m">7777</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">reliable</span>
      <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">8888</span>
      <span class="na">targetPort</span><span class="pi">:</span> <span class="m">8888</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>satisfactory-udp</strong> (LoadBalancer — game UDP traffic):</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">satisfactory-udp</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">satisfactory</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">metallb.universe.tf/allow-shared-ip</span><span class="pi">:</span> <span class="s2">"</span><span class="s">shared-external-ip"</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">LoadBalancer</span>
  <span class="na">loadBalancerIP</span><span class="pi">:</span> <span class="s2">"</span><span class="s">YOUR_PUBLIC_IP"</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">app.kubernetes.io/name</span><span class="pi">:</span> <span class="s">satisfactory</span>
  <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">game-udp</span>
      <span class="na">protocol</span><span class="pi">:</span> <span class="s">UDP</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">7777</span>
      <span class="na">targetPort</span><span class="pi">:</span> <span class="m">7777</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>satisfactory-frm</strong> (ClusterIP — internal FRM access from dashboard):</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">satisfactory-frm</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">satisfactory</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">ClusterIP</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">app.kubernetes.io/name</span><span class="pi">:</span> <span class="s">satisfactory</span>
  <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">frm-http</span>
      <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">8080</span>
      <span class="na">targetPort</span><span class="pi">:</span> <span class="m">8080</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">frm-ws</span>
      <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">8081</span>
      <span class="na">targetPort</span><span class="pi">:</span> <span class="m">8081</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<blockquote class="prompt-info">
  <p>With Kubernetes, FRM (ports 8080/8081) is accessible internally via ClusterIP.
You only need to open 8080/8081 on the host firewall if you want external FRM access.</p>
</blockquote>

<hr />

<h2 id="dashboard-stack-namespace-satisfactory-dashboard">Dashboard Stack (namespace: <code class="language-plaintext highlighter-rouge">satisfactory-dashboard</code>)</h2>

<h3 id="components">Components</h3>

<table>
  <thead>
    <tr>
      <th>Component</th>
      <th>Type</th>
      <th>Image</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>PostgreSQL</td>
      <td>StatefulSet</td>
      <td>postgres:15</td>
      <td>Time-series metrics database (with TimescaleDB)</td>
    </tr>
    <tr>
      <td>Redis</td>
      <td>Deployment</td>
      <td>redis:7</td>
      <td>Queue, cache, session storage</td>
    </tr>
    <tr>
      <td>Web</td>
      <td>Deployment</td>
      <td>ocholoko888/satisfactory-dashboard:latest</td>
      <td>nginx + PHP-FPM (Laravel app)</td>
    </tr>
    <tr>
      <td>Reverb</td>
      <td>Deployment</td>
      <td>(same)</td>
      <td>WebSocket server (Laravel Reverb)</td>
    </tr>
    <tr>
      <td>Horizon</td>
      <td>Deployment</td>
      <td>(same)</td>
      <td>Queue worker (Laravel Horizon)</td>
    </tr>
    <tr>
      <td>Scheduler</td>
      <td>Deployment</td>
      <td>(same)</td>
      <td>Cron runner (polling jobs)</td>
    </tr>
  </tbody>
</table>

<h3 id="web-pod-init-container">Web Pod Init Container</h3>

<p>The web pod runs an init container that migrates and seeds the database on startup:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="na">initContainers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">init-migrate</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">ocholoko888/satisfactory-dashboard:latest</span>
    <span class="na">command</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">sh"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">-c"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">php</span><span class="nv"> </span><span class="s">artisan</span><span class="nv"> </span><span class="s">migrate</span><span class="nv"> </span><span class="s">--force</span><span class="nv"> </span><span class="s">&amp;&amp;</span><span class="nv"> </span><span class="s">php</span><span class="nv"> </span><span class="s">artisan</span><span class="nv"> </span><span class="s">db:seed</span><span class="nv"> </span><span class="s">--class=ServerSeeder</span><span class="nv"> </span><span class="s">--force"</span><span class="pi">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="web-pod-resources">Web Pod Resources</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="na">resources</span><span class="pi">:</span>
  <span class="na">requests</span><span class="pi">:</span>
    <span class="na">cpu</span><span class="pi">:</span> <span class="s">250m</span>
    <span class="na">memory</span><span class="pi">:</span> <span class="s">256Mi</span>
  <span class="na">limits</span><span class="pi">:</span>
    <span class="na">cpu</span><span class="pi">:</span> <span class="s2">"</span><span class="s">1"</span>
    <span class="na">memory</span><span class="pi">:</span> <span class="s">512Mi</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="traefik-ingress-with-tls">Traefik Ingress with TLS</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
</pre></td><td class="rouge-code"><pre><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">dashboard-ingress</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">satisfactory-dashboard</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">cert-manager.io/cluster-issuer</span><span class="pi">:</span> <span class="s">cloudflare-clusterissuer</span>
    <span class="na">traefik.ingress.kubernetes.io/router.entrypoints</span><span class="pi">:</span> <span class="s">websecure</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">tls</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">satisfactory-dashboard.yourdomain.com</span>
      <span class="na">secretName</span><span class="pi">:</span> <span class="s">dashboard-tls</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">satisfactory-dashboard.yourdomain.com</span>
      <span class="na">http</span><span class="pi">:</span>
        <span class="na">paths</span><span class="pi">:</span>
          <span class="c1"># WebSocket path for Reverb</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">/app</span>
            <span class="na">pathType</span><span class="pi">:</span> <span class="s">Prefix</span>
            <span class="na">backend</span><span class="pi">:</span>
              <span class="na">service</span><span class="pi">:</span>
                <span class="na">name</span><span class="pi">:</span> <span class="s">dashboard-reverb</span>
                <span class="na">port</span><span class="pi">:</span>
                  <span class="na">number</span><span class="pi">:</span> <span class="m">8080</span>
          <span class="c1"># Everything else to the web service</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
            <span class="na">pathType</span><span class="pi">:</span> <span class="s">Prefix</span>
            <span class="na">backend</span><span class="pi">:</span>
              <span class="na">service</span><span class="pi">:</span>
                <span class="na">name</span><span class="pi">:</span> <span class="s">dashboard-web</span>
                <span class="na">port</span><span class="pi">:</span>
                  <span class="na">number</span><span class="pi">:</span> <span class="m">80</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<hr />

<h2 id="timescaledb-setup">TimescaleDB Setup</h2>

<p>PostgreSQL must have TimescaleDB enabled. The migrations create TimescaleDB hypertables
automatically, but the extension must be pre-installed. Apply a PostgreSQL ConfigMap
with:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="na">shared_preload_libraries</span><span class="pi">:</span> <span class="s">timescaledb</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Or use a TimescaleDB Docker image instead of plain <code class="language-plaintext highlighter-rouge">postgres:15</code>.</p>

<hr />

<h2 id="auto-update-with-keel">Auto-Update with Keel</h2>

<p>Keel polls Docker Hub every 6 hours and automatically deploys new images when a newer
<code class="language-plaintext highlighter-rouge">latest</code> tag is available:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="na">annotations</span><span class="pi">:</span>
  <span class="na">keel.sh/policy</span><span class="pi">:</span> <span class="s">force</span>
  <span class="na">keel.sh/trigger</span><span class="pi">:</span> <span class="s">poll</span>
  <span class="na">keel.sh/pollSchedule</span><span class="pi">:</span> <span class="s2">"</span><span class="s">@every</span><span class="nv"> </span><span class="s">6h"</span>
  <span class="na">keel.sh/approvals</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This means deployments update automatically without manual intervention.</p>

<hr />

<h2 id="see-also">See Also</h2>

<ul>
  <li><a href="/FicsitDocumentation/posts/environment-variables/">Environment Variables Reference</a> — configure the Laravel app</li>
  <li><a href="/FicsitDocumentation/posts/run-satisfactory-docker/">Running with Docker</a> — simpler Docker-based setup</li>
</ul>]]></content><author><name></name></author><category term="deployment" /><category term="satisfactory" /><category term="ficsitmonitor" /><category term="kubernetes" /><category term="k8s" /><category term="deployment" /><category term="traefik" /><category term="cert-manager" /><summary type="html"><![CDATA[Deploy FICSIT.monitor and a Satisfactory game server on Kubernetes. Full manifest walkthrough for namespaces, StatefulSets, services, Traefik ingress, and TLS.]]></summary></entry></feed>