No description
  • Python 43.6%
  • Shell 22.3%
  • JavaScript 13.9%
  • CSS 13.6%
  • HTML 6.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-13 11:52:54 +01:00
apache Logic error with the way it was building. Was only providing the json output. 2026-08-08 17:32:30 +01:00
dashboard Fixing appearance options 2026-08-12 01:07:43 +01:00
docs Fixing slow polling with tmux socket 2026-08-11 23:50:02 +01:00
examples/blueprints Fixing appearance options 2026-08-12 01:07:43 +01:00
nginx Logic error with the way it was building. Was only providing the json output. 2026-08-08 17:32:30 +01:00
profiles Improve desktop dashboard rendering performance 2026-08-13 11:52:54 +01:00
runtime Fixing appearance options 2026-08-12 01:07:43 +01:00
blueprint.schema.json Fixing appearance options 2026-08-12 01:07:43 +01:00
install.sh Fixing slow polling with tmux socket 2026-08-11 23:50:02 +01:00
README.md Fixing FPS and realm world times in palworld dashboard 2026-08-13 09:52:34 +01:00
uninstall.sh Adding many functions 2026-08-11 18:02:56 +01:00
VERSION Improve desktop dashboard rendering performance 2026-08-13 11:52:54 +01:00

GameOps Host Dashboard

GameOps turns a reviewed game-server blueprint into an authenticated operations dashboard for an existing Linux game server. It supports server state, uptime, lifecycle controls, bounded log output, optional player totals, updates, HTTPS, and login protection.

The Host Dashboard is the second part of the GameOps workflow:

Blueprint Builder website
  -> download <id>.blueprint.json
  -> copy and review it on the game host
  -> install.sh --blueprint <file>
  -> sign in to the operational dashboard

The Builder is separate because configuration fields include accounts, executables, service names, and host paths. A public browser must not be able to turn those fields directly into root-level host changes. The installer validates the reviewed file locally before it creates anything.

Companion project

Create or regenerate blueprints with the GameOps Blueprint Builder. Its downloaded JSON is the reviewed input to this repository's installer.

Features

  • Authenticated dashboard with state, uptime, lifecycle controls, and logs.
  • Player counts from a local Steam A2S query port or an existing HTTP JSON endpoint.
  • Immediate lifecycle acknowledgements with responsive transition status while the selected game manager completes start, stop, restart, or update operations.
  • State-aware controls: lifecycle actions are enabled only in valid states, while an update may run from either stable running or stopped state.
  • systemd, LinuxGSM, Docker Compose, named container, GNU screen, and custom-binary adapters.
  • Optional SteamCMD or fixed-command updates.
  • journald, file, and container log sources.
  • Apache, Nginx, or loopback-only installation modes.
  • Explicit TLS certificate and private-key paths.
  • fail2ban protection for dashboard login failures: optional on the universal board and required by every tailored board.
  • Multiple game dashboards on one host when project IDs, ports, and hostnames are unique.
  • Re-runnable upgrades that preserve login credentials unless reset.
  • Complete removal with uninstall.sh -a while leaving game data untouched.
  • A universal GameOps board plus built-in, game-locked FFXI/LandSandBoat, Palworld, and Minecraft Vanilla boards with their native controls.
  • Shared post-install appearance settings for the universal board, including bounded background artwork and optional colours suggested in the browser.
  • Up to eight fixed-format telemetry widgets from the configured HTTP JSON source, without custom markup or code.

What GameOps does not install

The game server must already exist. GameOps does not install or create the game, LinuxGSM, SteamCMD, Docker, game accounts, worlds, saves, DNS records, TLS certificates, Apache, or Nginx. When the universal blueprint enables fail2ban, the installer installs the host package when necessary and manages one dashboard-specific filter and jail. The Palworld and Minecraft boards can create their documented protected backups after installation; GameOps does not replace a tested off-host backup policy.

GameOps adopts the service, script, container, or commands named in the blueprint. Test those components before installing the dashboard.

Requirements

  • A systemd-based Linux host, such as Ubuntu or Debian.
  • Root access for installation and removal.
  • Python 3, Bash, systemctl, systemd-run, runuser, and standard Linux account tools.
  • An existing game server and the accounts/paths described by the blueprint.
  • Apache 2 or Nginx for the built-in public edge, or your own HTTPS reverse proxy with edge.proxy: "none".
  • A DNS hostname pointing to the host and a certificate covering that exact hostname.
  • A working package manager and package repositories if the universal board requests fail2ban or GNU screen and the required package is not installed. Tailored boards require an existing valid fail2ban installation.

The universal board needs no Node.js, database, application server, or ongoing sudo rule. Tailored boards keep their own fixed native helper boundary and may require Docker Compose/MariaDB, GNU screen/Palworld REST, or Minecraft RCON as described below. Every board binds to the blueprint's loopback address and port.

Dashboard boards

schema_version: "1.3" added the required top-level profile. Version 1.4 extends the universal default board with GNU screen, custom telemetry widgets, background artwork, and build reporting. Version 1.5 adds the explicit fast LinuxGSM tmux status probe. Tailored profiles remain on 1.3, and blueprints using 1.0-1.4 remain valid.

Default GameOps

Use profile.id: "default" for Valheim or any server using the generic runtime, updater, log, and telemetry adapters documented below. profile.options.appearance seeds the installed colour preset, accents, background, density, and corner style. After sign-in, Appearance changes those allowlisted values for the whole dashboard without editing the blueprint or restarting the game. It can also store one PNG or JPEG background and suggest a palette from that image; the operator can accept or adjust each suggested colour before saving. Every colour can be entered directly as #RRGGBB or adjusted with the built-in RGB sliders, so the dashboard does not rely on an operating-system colour dialog.

The palette list starts with GameOps and then Nexus. Their manifest identifiers are gameops and nexus. Existing violet manifests and saved state remain valid and are normalized to Nexus when loaded.

FFXI / LandSandBoat

Use profile.id: "ffxi" only for the expected LandSandBoat Docker Compose stack. The board shows database/connect/search/world/map state, health, CPU, memory, realm uptime, characters, zones, and selected Compose logs. It starts and stops the named services and restarts the game services while leaving the database running. It intentionally has no update action.

Before installation, verify the Compose stack exposes services named exactly database, connect, search, world, and map, and that the MariaDB container exposes its normal MARIADB_* variables and expected LandSandBoat accounts_sessions/chars tables. Those names are fixed because the native UI has dedicated health and resource panels for each service.

Palworld

Use profile.id: "palworld" only for a Palworld dedicated server controlled by the configured GNU screen session or systemd unit. The native board includes the Palworld build, Steam release check/update, protected save backup, console logs, and optional REST players/FPS/world metrics plus graceful saves and player warnings. Supply the AdminPassword at install time with --game-secret-file, or omit it for the basic non-REST feature set. FPS is shown as a rolling live history. Palworld REST reports the world-day counter but not an in-world hour, so the board deliberately labels that counter as live instead of inventing a sun/moon position.

Minecraft Vanilla

Use profile.id: "minecraft" only for Vanilla Minecraft Java Edition under systemd. The native board adopts the reviewed unit and provides player names, RCON console access, JVM/process details, journal streams, verified official stable-release upgrades, backup, and rollback. Installation may update server.properties, rotate the protected RCON credential, and replace the selected service unit with its reviewed managed form. It is not a generic Paper, Fabric, Forge, or modpack updater.

Every tailored profile requires acknowledged_game_lock: true, a matching project ID, edge.fail2ban: true, an installed and valid fail2ban service, and an explicit install flag:

sudo ./install.sh \
  --blueprint palworld.blueprint.json \
  --confirm-tailored-profile palworld

Profile code is included in the Host Dashboard release and selected from a fixed registry. A blueprint cannot load a remote plug-in, local template path, script, CSS file, or arbitrary command. Passwords and API/RCON secrets are collected separately and never stored in JSON.

The three tailored IDs are intentionally singular, but different boards can coexist on one host: for example, ffxi, palworld, and minecraft may be installed together when their loopback ports and public hostnames are unique. They may also coexist with universal boards that use other project IDs.

Quick start: Valheim with LinuxGSM

The included examples/blueprints/valheim-linuxgsm.blueprint.json expects:

game account:       vhserver
working directory:  /home/vhserver
LinuxGSM script:    /home/vhserver/vhserver
tmux session:       vhserver
console log:        /home/vhserver/log/console/vhserver-console.log
Steam query port:   2457/UDP (local only for dashboard telemetry)

Review the example and change its project identity, hostname, port, region, and paths to match the host. For LinuxGSM, script is the executable path only; the adapter supplies start, stop, and restart. status.session_name names the tmux session created by LinuxGSM, normally the script basename.

Then install it:

chmod +x install.sh uninstall.sh

sudo ./install.sh \
  --blueprint examples/blueprints/valheim-linuxgsm.blueprint.json \
  --tls-cert /etc/letsencrypt/live/example.com/fullchain.pem \
  --tls-key /etc/letsencrypt/live/example.com/privkey.pem

The first installation prompts for a password of at least 12 characters. The default username is operator. Open the blueprint's dashboard.public_host URL and sign in.

For non-interactive installation, place the initial password on the first line of a root-readable file:

sudo ./install.sh \
  --blueprint valheim.blueprint.json \
  --password-file /root/gameops-initial-password

Remove the password file after installation.

Installer options

Usage: sudo ./install.sh --blueprint FILE [options]

  --blueprint FILE       Blueprint JSON produced by the Builder.
  --public-host HOST     Override dashboard.public_host in the installed copy.
  --proxy MODE           apache, nginx, or none. Default: blueprint edge.proxy.
  --tls-cert PATH        TLS certificate chain.
  --tls-key PATH         TLS private key.
  --username NAME        Dashboard login name. Default: operator.
  --password-file FILE   Read the initial/reset password from the first line.
  --game-secret-file P   Palworld REST/AdminPassword file for that profile.
  --confirm-tailored-profile ID
                         Confirm the reviewed ffxi, palworld, or minecraft lock.
  --reset-auth           Replace the password and invalidate sessions.
  --no-fail2ban          Skip requested fail2ban integration (default board only).
  --no-reload            Validate proxy/fail2ban config without reloading it.
  --no-start             Install without enabling or starting the services.
  -h, --help             Show command help.

The hostname normally comes from the blueprint. --public-host lets the same reviewed profile be promoted to another hostname.

When Apache or Nginx is selected, the default Certbot paths are:

/etc/letsencrypt/live/<public-host>/fullchain.pem
/etc/letsencrypt/live/<public-host>/privkey.pem

Use --tls-cert and --tls-key when a wildcard or multi-domain certificate is stored under another certificate name. The certificate must still include the dashboard hostname.

Nexus Game Fleet connection

When Nexus Game Fleet runs on the same host, set its Internal control URL to the dashboard's loopback listener:

http://127.0.0.1:<dashboard.port>

For the included Valheim example that is http://127.0.0.1:8788. If Nexus is inside a container, that container's 127.0.0.1 is not the host; use host networking, a deliberately reachable internal address, or leave the field blank so Nexus uses the public HTTPS dashboard URL. Do not expose the raw loopback API directly to an untrusted network.

Choose Nexus profile standard for the universal GameOps board, or the matching ffxi, palworld, or minecraft profile for a tailored board. Supply the dashboard password. The universal, Palworld, and Minecraft APIs accept Nexus's password-only login; FFXI also requires its configured dashboard username. Lifecycle requests return immediately and Nexus observes the temporary starting/stopping state through status polling, so a long game shutdown or startup does not make the dashboard card unavailable.

Blueprint fields

Identity and dashboard

  • project.id is the stable installation ID: 2-22 lowercase letters, digits, or hyphens, starting with a letter.
  • project.name, project.game, and project.region appear in the UI.
  • dashboard.bind must be 127.0.0.1.
  • dashboard.port must be unused and unique on the host.
  • dashboard.public_host must be the exact DNS hostname used in the browser.
  • security.dashboard_user must be <project.id>-dashboard.

Changing project.id creates another dashboard instead of upgrading the old one.

Profile

  • profile.id is default, ffxi, palworld, or minecraft.
  • Tailored profiles require acknowledged_game_lock: true and lock project.id to the same value because their native service names and state ownership are intentionally singular.
  • profile.options contains only bounded, non-secret settings for that board.
  • Tailored blueprints use { "adapter": "profile" } for runtime/log/telemetry and, where supported, updater. The Host installer dispatches these to the selected built-in native adapter; the generic control helper never interprets them.

Runtime: systemd

Controls one existing service and reads its ActiveState:

{ "adapter": "systemd", "service": "my-game.service" }

Runtime: LinuxGSM

Calls an existing LinuxGSM script as its non-root game account. Passive status uses one local process-table lookup for the configured tmux session. It does not invoke LinuxGSM details, contact public-IP services, or use monitor. The same probe supplies the current game-session age rather than host uptime.

{
  "adapter": "linuxgsm",
  "user": "vhserver",
  "working_directory": "/home/vhserver",
  "script": "/home/vhserver/vhserver",
  "status": {
    "adapter": "tmux",
    "session_name": "vhserver"
  }
}

Schema 1.5 requires the explicit status object. Existing schema 1.0-1.4 LinuxGSM blueprints remain compatible and use the script basename as the tmux session name.

Runtime: Docker Compose or named container

docker-compose fixes one project directory and name. container fixes one Docker or Podman container. The browser cannot choose another project, container, or engine.

Runtime: GNU screen

Use screen for a game kept alive in one named GNU screen session. The adapter runs one fixed launch argument array as the configured game account, captures the session log, derives uptime from the exact session PID, and sends Ctrl-C for a graceful stop. It does not accept a command from the browser and never uses a shell wrapper.

{
  "adapter": "screen",
  "user": "gameserver",
  "working_directory": "/srv/my-game/server",
  "screen_binary": "/usr/bin/screen",
  "session_name": "my-game",
  "launch_argv": ["/srv/my-game/server/GameServer", "--port", "7777"],
  "log_file": "/srv/my-game/logs/console.log",
  "stop_timeout_seconds": 120
}

The runtime account, working directory, executable, log parent directory, and any updater paths must already exist. The installer can add the GNU screen package, but it does not create or download the game server.

Runtime: custom binary

Use exec when no supported manager exists. Every command is an argument array: the first item is an absolute executable, and every later item is one argument. A read-only status command and running/stopped exit codes are required.

{
  "adapter": "exec",
  "user": "gameserver",
  "working_directory": "/srv/my-game",
  "start_argv": ["/srv/my-game/server", "--port", "7777"],
  "stop_argv": ["/usr/local/libexec/my-game-stop"],
  "status_argv": ["/usr/local/libexec/my-game-status"],
  "status": {
    "running_exit_codes": [0],
    "stopped_exit_codes": [3]
  }
}

Do not write "/home/vhserver/vhserver start" as one item. It would be treated as the executable filename. Use "/home/vhserver/vhserver", "start" as two items. Shell operators, pipes, redirection, and command substitution are not supported.

Game-account commands run in transient systemd services. This allows a daemon started by LinuxGSM or a custom adapter to remain outside the dashboard service's lifecycle and sandbox. LinuxGSM passive status is a five-second local process-table lookup; it does not launch details. Custom status probes have a fixed runtime limit and are terminated as a complete process group if they do not finish. Start, stop, restart, and update commands retain the daemon-friendly process behavior required by game managers.

Updates

Choose disabled, linuxgsm, steamcmd, or a fixed exec updater. Selecting disabled removes the complete Server updates section from the deployed dashboard.

For an existing LinuxGSM server, select LinuxGSM in the Builder. GameOps reads the installed Steam build from the app manifest, uses the fixed SteamCMD executable to check the public build, and invokes the existing LinuxGSM update action only after an authenticated operator confirms:

{
  "adapter": "linuxgsm",
  "executable": "/home/vhserver/.local/share/Steam/steamcmd/steamcmd.sh",
  "app_id": "896660",
  "app_manifest": "/home/vhserver/serverfiles/steamapps/appmanifest_896660.acf"
}

Valheim Dedicated Server uses Steam App ID 896660. Confirm the two paths on the target host; LinuxGSM installations can place SteamCMD differently. The runtime account, working directory, and vhserver script come from the LinuxGSM runtime section, so they are not entered twice.

The Builder's Advanced custom option is for unsupported update systems. Its Update command performs the update. The optional Installed build command and Available build command are read-only probes: each must print one build identifier containing only letters, digits, ., _, +, or - on its final nonblank line. The JSON contract stores these fixed argument arrays as argv, current_build_argv, and available_build_argv; operators do not need to use those names in the Builder.

Only add update to security.allowed_web_actions when an updater is enabled. GameOps does not create backups; arrange and test world backups before enabling web-triggered updates.

Logs

Logs may come from one journald unit, absolute file path, or named container. The control service returns only the configured trailing lines and caps the response size. The UI renders log output as text, never HTML.

Player telemetry

For a game that exposes the Steam server-query protocol, use steam-a2s with the local UDP query port:

{
  "adapter": "steam-a2s",
  "host": "127.0.0.1",
  "port": 2457
}

The host is deliberately fixed to loopback. No public query or arbitrary remote address is accepted by this adapter. The game must bind its query port locally and respond to A2S_INFO. For Valheim, the default game port is 2456 and the default query port is 2457. Confirm the actual value with:

sudo -u vhserver -H /home/vhserver/vhserver details
sudo ss -lunp | grep ':2457'

Valheim's Steam backend supports the Steam query path. A server started with -crossplay uses the PlayFab backend, so Steam A2S data may not be available; select HTTP JSON if another local status service supplies player counts, or select none to show an honest unknown value.

For a configured HTTP JSON endpoint, dot-separated mappings select the two player values:

{
  "adapter": "http-json",
  "url": "http://127.0.0.1:9090/status",
  "mapping": {
    "players_online": "players.online",
    "players_capacity": "players.capacity"
  }
}

If the selected query interface or HTTP endpoint is absent, blocked, still starting, or returns an unexpected response, the dashboard shows player totals as unavailable and displays the telemetry error. It never converts an unknown count into zero.

Player telemetry runs only after the runtime adapter reports running. A stopped server displays dashes for players, uptime, and custom telemetry without sending a UDP or HTTP query. Starting and stopping transitions also leave those values blank until the operation finishes.

Custom telemetry widgets

Schema 1.4 can add up to eight cards to the universal board when telemetry uses http-json. Each widget reads one dot-separated path from that same bounded response:

"dashboard": {
  "bind": "127.0.0.1",
  "port": 8788,
  "public_host": "game.example.com",
  "widgets": [
    {
      "id": "server-fps",
      "label": "Server FPS",
      "source": "metrics.server_fps",
      "format": "decimal",
      "unit": "FPS",
      "description": "Reported by game service"
    },
    {
      "id": "world-day",
      "label": "World day",
      "source": "world.day",
      "format": "integer"
    }
  ]
}

Supported formats are integer, decimal, percent, duration, text, and boolean. Labels, units, descriptions, paths, values, and card count are bounded. Widgets cannot contain HTML, JavaScript, CSS, expressions, icons, or their own URLs. Missing or incorrectly typed values display as unavailable.

Uptime sources

Uptime is obtained from the selected runtime rather than from player telemetry:

  • systemd: time since the unit entered its active state;
  • LinuxGSM: elapsed time of the matching named tmux server session;
  • GNU screen: elapsed time of the exact configured screen session process;
  • named Docker/Podman container: time since the container's recorded start;
  • Docker Compose and custom exec: unavailable unless a future adapter can identify one unambiguous long-lived process.

This keeps host uptime, dashboard-service uptime, and game-server uptime from being confused with each other.

What installation changes

For project.id: "valheim", the installer manages:

/opt/valheim-dashboard/                                  dashboard app
/etc/gameops-dashboard/valheim/blueprint.json           installed blueprint
/etc/gameops-dashboard/valheim/dashboard.env            login settings
/etc/gameops-dashboard/valheim/install.conf             removal metadata
/var/lib/gameops-dashboard/valheim/appearance.json      shared appearance state
/var/lib/gameops-dashboard/valheim/appearance-background.img
                                                        optional private background
/var/lib/gameops-dashboard/valheim/                     session and UI state
/usr/local/libexec/valheim-dashboard-control            control program
/etc/systemd/system/valheim-dashboard-control.service   root control service
/etc/systemd/system/valheim-dashboard.service           loopback web service
/run/gameops-dashboard/valheim.sock                     local control socket

Apache adds:

/etc/apache2/sites-available/valheim-dashboard.conf
/etc/apache2/sites-enabled/valheim-dashboard.conf

Nginx uses equivalent sites-available and sites-enabled entries. Requested fail2ban files use the same project prefix under filter.d and jail.d.

Tailored profiles install their native board under the profile's established /opt, /etc, /var/lib, systemd, helper, and proxy names. The wrapper records /etc/gameops-dashboard-profile/<id>.conf so this package's uninstaller can delegate safely to the matching built-in profile. The installer validates the profile's managed services, paths, endpoints, and non-secret options before handing them to its fixed native integration.

Verify an installation

Replace valheim and the URL for another project:

sudo systemctl status valheim-dashboard-control.service
sudo systemctl status valheim-dashboard.service
sudo journalctl -u valheim-dashboard-control.service -n 100 --no-pager
sudo journalctl -u valheim-dashboard.service -n 100 --no-pager
sudo apache2ctl -S | grep valheim
curl -I https://valheim.example.com/
curl https://valheim.example.com/healthz

Test the privilege boundary exactly as the dashboard account:

sudo -u valheim-dashboard \
  /usr/bin/python3 /opt/valheim-dashboard/runtime/server.py \
  --check-control-socket /run/gameops-dashboard/valheim.sock

Upgrade or change the blueprint

Regenerate or edit the blueprint, review the changes, and rerun the installer:

sudo ./install.sh --blueprint valheim.blueprint.json

The dashboard, installed blueprint, services, control boundary, and proxy site are updated. The username, password hash, and signing key are preserved. Use --reset-auth or --password-file to replace them.

The default board's saved appearance is also preserved. Change it after sign-in with Appearance; reset there to restore the blueprint seed. The server accepts only the documented presets, colours, density, corner values, and a background-image flag. PNG/JPEG uploads are authenticated, limited to 5 MiB, checked for 320x180 to 4096x4096 dimensions (and at most 12 megapixels), stored privately, and served from one fixed same-origin path. It never adds an uploaded URL or weakens the Content Security Policy.

Rerunning a tailored profile upgrades that same native board and preserves its credentials according to the profile installer. Switching profiles in place is refused because helpers, credentials, state, and game ownership differ. Purge that dashboard with uninstall.sh --id <id> -a, review the new blueprint, then install the new profile. Removing or changing one tailored ID does not affect a different tailored or universal dashboard on the same host.

On every board, lifecycle API requests are acknowledged after validation and queued in the dashboard service. They do not hold an HTTP connection open while the game manager finishes. During the operation, /api/status remains responsive and includes the active native transition/operation; another action receives HTTP 409 until it completes. Tailored boards retain their native status and feature payloads while following that non-blocking lifecycle contract.

The universal board also prevents invalid lifecycle requests in its interface. Start is available only when the runtime is stopped. Stop and restart are available only when it is running. Update is available in either stable state, and all controls remain disabled while an operation or update check is in progress. Stop, restart, and update require confirmation.

Version 0.3.1 replaces the earlier sudo-based helper path with the local control service. Rerunning the 0.3.1 installer removes the old dashboard sudo rule, installs the new service, verifies the Unix socket as the dashboard account, and restarts the dashboard.

The proxy mode cannot be changed in place. Remove that dashboard instance and install it again with the new mode.

If the same hostname is still occupied by an older questionnaire-only install, remove that old site first:

sudo ./uninstall.sh --legacy -a

Uninstall

Remove the dashboard application and host integration while preserving its blueprint, credentials, metadata, service account, and dashboard state:

sudo ./uninstall.sh --id valheim

Remove all GameOps-owned data for that dashboard:

sudo ./uninstall.sh --id valheim -a
# --all is equivalent

Both modes leave the game account, installation, saves, worlds, backups, and game logs untouched.

The same commands dispatch ffxi, palworld, and minecraft to their bundled native uninstallers when profile metadata is present. Normal removal preserves the native board's credentials/configuration where supported and keeps the small profile-routing record so reinstall and a later -a still dispatch to the correct uninstaller. -a removes that board's dashboard-owned state, credentials, routing record, and service account. Protected game backups are retained even by complete dashboard removal.

Security boundaries

  • The installed blueprint is root-owned and not writable by the web account.
  • On the universal board, the web service runs as <id>-dashboard, with no login shell, game-account membership, or sudo rule.
  • Its root control service listens only on a local Unix socket owned by root:<id>-dashboard with mode 0660.
  • That control service verifies the connecting UID using Linux SO_PEERCRED.
  • Every request is a small fixed JSON operation. The authoritative blueprint is reloaded and validated before the operation runs.
  • The browser can send only a fixed action name already allowlisted by the blueprint; it cannot provide commands, paths, arguments, or accounts.
  • The API requires login and a per-session CSRF token for mutations.
  • Passwords use salted scrypt hashes. Session cookies are signed, HTTP-only, same-site, and secure.
  • TLS private keys remain with the reverse proxy and are never copied into the dashboard directories.
  • Logs and telemetry are untrusted data and are never interpreted as markup or commands.
  • Tailored boards retain their audited native privilege boundary. Where a narrowly scoped sudo rule is required, it permits only the bundled root-owned helper and fixed operation names; browser data never becomes a command, service, session, path, Compose argument, RCON credential, or update target.
  • Appearance state accepts only allowlisted data and is written atomically under /var/lib; it cannot inject CSS, markup, URLs, or executable content. Background uploads accept only bounded PNG/JPEG data and are served with a fixed image content type and same-origin resource policy.
  • Custom widgets are fixed text cards mapped to primitive values in the configured telemetry response. They cannot define markup, scripts, styles, URLs, expressions, or commands.

This limits the dashboard's authority but does not make a public administration tool risk-free. Keep the host patched, use a strong unique password, restrict network access where practical, and retain tested backups.

Troubleshooting

The hostname does not resolve

Check the exact hostname in the blueprint and browser. .com and .co.uk are different DNS zones:

getent ahosts valheim.example.com

The TLS certificate is stored under a different name

Pass the real paths with --tls-cert and --tls-key. The certificate must still cover the public hostname.

Apache has a site but the dashboard does not load

sudo apache2ctl -S
sudo systemctl status valheim-dashboard.service
sudo journalctl -u valheim-dashboard.service -n 100 --no-pager
sudo tail -n 100 /var/log/apache2/valheim-dashboard-error.log

Also confirm that DNS resolves the hostname shown in the browser. A virtual host can be correct while the requested name has no DNS record.

The hostname opens another site or redirects to a different login

Check which program actually owns the public HTTP and HTTPS ports:

sudo ss -ltnp | grep -E ':(80|443)[[:space:]]'
sudo systemctl is-active apache2 nginx

The selected GameOps proxy must be the service accepting the public request. Do not select the proxy from the HTTP Server header alone. An Apache virtual host may reverse-proxy an unmatched request to a container such as Heimdall, whose Nginx server then supplies that header.

If Apache owns the host ports but another site appears, inspect the enabled and parsed virtual hosts:

sudo ls -l /etc/apache2/sites-enabled/valheim-dashboard.conf
sudo apache2ctl -S | grep -A3 -B3 valheim.example.com

The installer reloads Apache only after the control service and dashboard account pass their startup checks. If installation stops before that point, Apache continues using its previous configuration and may route the hostname through its default virtual host. Correct the reported installation error and rerun the installer with --proxy apache; a successful run performs the reload.

If neither locally selected proxy owns the public ports, check the DNS target, router port-forward, container publishing, or upstream reverse proxy before reinstalling.

Status, controls, and logs show a sudo/no-new-privileges error

That message comes from the older helper design. Install this version again with the same blueprint:

sudo ./install.sh --blueprint valheim.blueprint.json \
  --tls-cert /etc/letsencrypt/live/example.com/fullchain.pem \
  --tls-key /etc/letsencrypt/live/example.com/privkey.pem

Then verify both services and the socket:

sudo systemctl status valheim-dashboard-control.service
sudo journalctl -u valheim-dashboard-control.service -n 100 --no-pager
sudo -u valheim-dashboard \
  /usr/bin/python3 /opt/valheim-dashboard/runtime/server.py \
  --check-control-socket /run/gameops-dashboard/valheim.sock

LinuxGSM status is unavailable

Confirm the configured session name matches the LinuxGSM tmux process:

sudo ps -u vhserver -o etimes=,args= | grep -E 'tmux .* -s vhserver( |$)'

The check is a read-only local ps query with a five-second ceiling. No vhserver details process is created during routine dashboard or Nexus refreshes. A missing exact session reports the server as stopped.

To inspect the configured value after installation:

sudo grep -A5 '"status"' /etc/gameops-dashboard/valheim/blueprint.json

For exec, run the configured status command as its game account and verify that the exit code matches running_exit_codes or stopped_exit_codes.

Logs are unavailable

Confirm the installed blueprint contains the exact path and the game has created the file. The root control service reads that configured source; the web account does not need direct access to game logs.

Player totals are unavailable

First inspect the installed telemetry choice:

sudo grep -A6 '"telemetry"' /etc/gameops-dashboard/valheim/blueprint.json

For steam-a2s, verify that the configured UDP port is listening and that the game is using a Steam-query-capable backend:

sudo ss -lunp | grep ':2457'
sudo -u vhserver -H /home/vhserver/vhserver details

The query port only needs to be reachable on 127.0.0.1 for the dashboard; router forwarding is a separate requirement for public game access. Valheim normally uses port 2457 when the game port is 2456. If the process was started with -crossplay, Steam A2S may be unavailable; use a real HTTP JSON telemetry service or set telemetry to none.

For http-json, request the configured URL locally and compare its JSON keys with the two mappings. The dashboard surfaces timeouts and malformed replies in the Players card instead of displaying a false zero.

Uptime is unavailable

For LinuxGSM, verify that the game is running in the expected account's named tmux session. Replace the account and script name for another installation:

sudo ps -u vhserver -o etimes=,args= | grep -E 'tmux .* -s vhserver( |$)'

The dashboard intentionally does not use the Uptime line in LinuxGSM details, because that value can describe the Linux host. A missing matching tmux session is treated as stopped, with player totals and uptime shown as dashes.