- Python 43.6%
- Shell 22.3%
- JavaScript 13.9%
- CSS 13.6%
- HTML 6.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| apache | ||
| dashboard | ||
| docs | ||
| examples/blueprints | ||
| nginx | ||
| profiles | ||
| runtime | ||
| blueprint.schema.json | ||
| install.sh | ||
| README.md | ||
| uninstall.sh | ||
| VERSION | ||
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 -awhile 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.idis the stable installation ID: 2-22 lowercase letters, digits, or hyphens, starting with a letter.project.name,project.game, andproject.regionappear in the UI.dashboard.bindmust be127.0.0.1.dashboard.portmust be unused and unique on the host.dashboard.public_hostmust be the exact DNS hostname used in the browser.security.dashboard_usermust be<project.id>-dashboard.
Changing project.id creates another dashboard instead of upgrading the old
one.
Profile
profile.idisdefault,ffxi,palworld, orminecraft.- Tailored profiles require
acknowledged_game_lock: trueand lockproject.idto the same value because their native service names and state ownership are intentionally singular. profile.optionscontains 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>-dashboardwith mode0660. - 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.