You are viewing a potentially older version of this package. View all versions.
Odin_Sons-ServerInfo-2.2.2 icon

ServerInfo

Exposes a small HTTP JSON API with a Valheim server's world name, connected players (with Steam avatars), and installed mods — built for live status pages that read it directly from the browser.

Date uploaded 2 weeks ago
Version 2.2.2
Download link Odin_Sons-ServerInfo-2.2.2.zip
Downloads 17
Dependency string Odin_Sons-ServerInfo-2.2.2

This mod requires the following mods to function

denikson-BepInExPack_Valheim-5.4.2350 icon
denikson-BepInExPack_Valheim

BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.

Preferred version: 5.4.2350
ValheimModding-JsonDotNET-13.0.4 icon
ValheimModding-JsonDotNET

Shared version 13.0.3 of Json.NET from Newtonsoft, net45 package for use in Valheim mods. Maintained by the ValheimModding team.

Preferred version: 13.0.4

README

Server Info

CI Publish

A BepInEx plugin for Valheim dedicated servers. Exposes a small HTTP API with the world name, connected players, and installed mods (enriched with each mod's manifest.json when it has one) — used by the project's status page to show live server state.

Installing

Copy ServerInfo.dll into the server's BepInEx/plugins/ folder and restart the server process — that's it. BepInEx generates BepInEx/config/Odin_Sons.ServerInfo.cfg on first run with every setting below at its default, so there's nothing else to set up before it works.

Network setup

The plugin runs its own HTTP server; it doesn't need nginx or any other reverse proxy in front of it. Two things to check on a fresh server:

  • Firewall / port forwarding. Whatever port you configure (8880 by default) needs to be reachable from wherever your status page or browser calls it from — open it in your firewall, and forward it on your router if the server is behind NAT.
  • Windows: URL reservation. HttpListener needs an admin-granted reservation before a non-admin process can bind a port for all hosts. This only controls which local Windows accounts may bind that port — it has no effect on network access, which is what the firewall step above is for. If the log shows an access error on startup, find the account actually running the server (whoami) and run once as administrator (replace 8880 and YOUR_USERNAME):
    netsh http add urlacl url=http://+:8880/ user=YOUR_USERNAME
    
    If you're not sure which account will run it (e.g. a service manager that may change this), user=Everyone always works but lets any local account on the machine claim that reservation too — fine on a single-user machine, worth avoiding on a shared one.

Cross-origin requests are controlled by two settings, both under [Server]:

  • Domain — the public domain or IP this server is reachable at. Empty by default. It isn't used by the plugin for anything except gating the next setting — think of it as "this server is ready to be public" rather than an address the plugin acts on.
  • AllowedOrigin — who's allowed to read the response from a browser. Defaults to * (any site), but is only actually sent once Domain is set — so a fresh install doesn't advertise a CORS policy for a server that isn't publicly reachable yet.

If your status page reads this endpoint directly from the browser, set Domain to this server's real address and, if you want to restrict who can read the response, set AllowedOrigin to the status page's exact origin (scheme+host+port, e.g. https://status.example.com — the browser requires an exact match, so a domain that merely points at the same server doesn't count as the same origin).

Configuration

Set via BepInEx/config/Odin_Sons.ServerInfo.cfg. Numeric settings declare an acceptable range — BepInEx clamps an out-of-range value in the file to the nearest bound instead of accepting it as-is, and any BepInEx config-editor mod (e.g. Configuration Manager) renders them as a bounded slider rather than free text.

[Server]

Key Type Default Range Meaning
Port Int32 8880 1–65535 HTTP port for the web server.
ServerInfoPath String /serverinfo URL path the server info endpoint is served on.
Domain String (empty) This server's public domain or IP. Gates AllowedOrigin — see below.
AllowedOrigin String * Value sent as Access-Control-Allow-Origin, only once Domain is set.
RequestTimeoutSeconds Int32 5 1–60 How long a request waits for the game's main thread before timing out.

[Cache]

Key Type Default Range Meaning
CacheIntervalSeconds Int32 5 1–3600 How often the server/player list is recomputed.
AvatarCacheMinutes Int32 60 1–1440 How long a fetched avatar URL is reused, and how long an inactive player's entry is kept.

[Steam]

Key Type Default Meaning
SteamApiKey String (empty) Optional; without it, the plugin still works — players is returned as usual, just without AvatarUrl.

[Mods]

Key Type Default Meaning
MetadataSources String Manifest,Assembly Comma-separated order of precedence for a mod's description/websiteUrl/dependencies — see below.

[Logging]

Key Type Default Meaning
LogLevel Flags enum Error, Warning Which message levels this plugin writes to the BepInEx log. Combine values in the .cfg (e.g. Error, Warning, Info) to include more detail; Info adds startup/diagnostic messages not needed for normal operation.

How the avatar cache works

Fetched avatar URLs are kept in memory, keyed by SteamID — not written to disk, and gone on restart. An entry younger than AvatarCacheMinutes is reused as-is; once it's older, the next request for that player fetches a fresh URL from Steam and replaces it. Entries nobody's asked for in over AvatarCacheMinutes are dropped during the next refresh, so a player who was online once doesn't sit in memory forever.

There's no live command to clear it early — restarting the server is the only way, since the cache doesn't survive that anyway. To make it refresh sooner across the board, lower AvatarCacheMinutes.

Example response

GET /serverinfo
{
  "name": "MyValheimServer",
  "playersCount": 1,
  "players": [
    {
      "Name": "Ari",
      "SteamID": "76561198000000000",
      "AvatarUrl": "https://avatars.steamstatic.com/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_full.jpg"
    }
  ],
  "mods": [
    {
      "name": "Conditional Config Sync",
      "guid": "_shudnal.ConditionalConfigSync",
      "version": "1.0.4",
      "description": "Shared config synchronization and server policy library for Valheim mods.",
      "websiteUrl": "https://github.com/shudnal/ConditionalConfigSync",
      "dependencies": ["denikson-BepInExPack_Valheim-5.4.2333"],
      "namespace": "shudnal",
      "packageName": "ConditionalConfigSync"
    },
    {
      "name": "BepInExPack_Valheim",
      "guid": "denikson.BepInExPack_Valheim",
      "version": "5.4.2333",
      "description": "BepInEx pack for Valheim.",
      "websiteUrl": "https://github.com/BepInEx/BepInEx",
      "dependencies": [],
      "namespace": "denikson",
      "packageName": "BepInExPack_Valheim"
    }
  ]
}

name/guid/version on a mod come from BepInEx itself, so they're always present. Where description/websiteUrl/dependencies come from is controlled by MetadataSources:

  • Manifest — the mod's own manifest.json (Thunderstore/Hexium package): description, website_url, dependencies.
  • Assembly — the mod's own compiled metadata: AssemblyDescription for description, an embedded RepositoryUrl (some build setups add this automatically) for websiteUrl, and its BepInEx dependency GUIDs for dependencies.

For each field, sources are tried in the configured order and the first non-empty value wins — so the default Manifest,Assembly prefers the manifest and only falls back to the assembly for mods that don't have one. Set it to Manifest to disable the assembly fallback entirely, to Assembly to ignore manifests altogether, or to Assembly,Manifest to prefer assembly metadata when both are present. A field is null if none of the configured sources have a value for it.

namespace/packageName aren't affected by MetadataSources — they're Thunderstore/Hexium package identity, so they only ever come from a manifest.json, or null without one.

namespace + packageName build a Thunderstore or Hexium mod page URL (both use the same shape):

  • https://thunderstore.io/c/valheim/p/{namespace}/{packageName}/
  • https://valheim.hexium.gg/mods/{namespace}/{packageName}

Credits

Based on PublicWebLink by Maddy.

Contributing

See CONTRIBUTING.md for building from source.

CHANGELOG

Changelog

All notable changes to this project are documented here.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning (with the BepInEx-safe caveat in CONTRIBUTING.md — no prerelease suffixes).

Unreleased

2.2.7 - 2026-09-13

Fixed

  • Stopping the server no longer logs a spurious ObjectDisposedException error from HandleLoopStopServer closing the HttpListener while a request is pending is expected during shutdown, not a real failure.

2.2.6 - 2026-09-13

Added

  • Licensed under GPL-3.0-or-later (see LICENSE, full official text). GPL-3.0 requires forks/redistributions to keep this project's copyright and license notices intact — those notices point back to https://github.com/odin-sons/serverinfo. Added the matching SPDX header to every source file and a License section to the README.
  • LICENSE is now included in the published package zip.

Changed

  • manifest.json's and thunderstore.toml's website_url now point to the repository instead of being empty.

2.2.5 - 2026-09-13

Added

  • VirusTotal scan of the built package before publishing to either platform, gated on the VIRUSTOTAL_API_KEY repository secret. A malicious/suspicious verdict fails the release before anything ships; the scan report link is added to the GitHub Release notes either way.
  • A VirusTotal badge in the README, linking to the latest release (where that release's own scan report is linked).

2.2.4 - 2026-09-13

Fixed

  • Removed the em dash from manifest.json's description — Thunderstore's manifest validator rejects it (confirmed by hand, undocumented). Uses a plain hyphen now; a test guards against it coming back.
  • CHANGELOG.md is now actually included in the published package zip. tcli build only copies icon/readme automatically; the changelog needed its own [[build.copy]] entry in thunderstore.toml.

2.2.3 - 2026-09-12

Fixed

  • Hexium publish uploaded a corrupted package ("Invalid ZIP file." from the server). PowerShell's $array[$a..$b] range-index isn't guaranteed to stay a [byte[]] — it silently decayed to a generic object[], which Invoke-WebRequest -Body sent as something other than raw bytes. Switched to [Array]::Copy into a real byte[].

2.2.2 - 2026-09-12

Fixed

  • tcli publish in the release pipeline now tolerates Thunderstore's "already exists" response instead of failing the job — a version that publishes there successfully but fails at a later step (e.g. Hexium) can now be safely re-run without the Thunderstore step blocking it.

2.2.1 - 2026-09-12

Added

  • Automated Hexium publishing in the release pipeline, alongside Thunderstore. Reverse engineered from Kesomannen/gale's open-source client against Hexium's own (undocumented) submission API: initiate a multipart upload, PUT the package bytes, finish the upload, then submit. No plugin code changes — release tooling only.

2.2.0 - 2026-09-12

Added

  • Unit test project (ServerInfo.Tests) covering the reflection helpers, ID/path parsing, and config parsing, plus packaging validation (manifest.json fields, icon.png dimensions, and version consistency across Program.cs/AssemblyInfo.cs/manifest.json/thunderstore.toml).
  • CI (.github/workflows/ci.yml): builds and runs the full test suite on every push and pull request.
  • Automated release pipeline (.github/workflows/publish.yml): pushing a v*.*.* tag builds, tests, and — only if that passes — publishes to Thunderstore and attaches the package zip to a GitHub Release for a manual Hexium upload (Hexium has no public upload API yet).
  • manifest.json, icon.png, thunderstore.toml, and package.ps1 for publishing to Thunderstore and Hexium (see CONTRIBUTING.md).

Changed

  • Breaking: no longer bundles its own copy of Newtonsoft.Json. Declares a dependency on the shared ValheimModding-JsonDotNET library instead, to avoid two copies of the same assembly being loaded at once. Installs through a mod manager pull this in automatically; a manual DLL-only install now needs that library present too.
  • Packaged files no longer sit under a plugins/ subfolder in the release zip — they ship flat at the package root instead, so manifest.json ends up next to the installed DLL (the plugin reads its own neighboring manifest.json at runtime; plugins/ gets flattened straight into BepInEx/plugins/ on install, separating the two).

[2.1.0] - 2026-09-12

Added

  • Domain config ([Server] section) — this server's public domain or IP. It's purely a readiness flag for the plugin: the Access-Control-Allow-Origin header is now only sent once Domain is set, so a fresh install doesn't advertise a CORS policy before the server is actually meant to be reachable from the outside. AllowedOrigin itself is unchanged (* by default).

[2.0.0] - 2026-09-12

Changed

  • Breaking: renamed the project from PublicWebLink to Server Info: plugin GUID changed to Odin_Sons.ServerInfo, display name to Server Info, assembly to ServerInfo.dll, namespace to ServerInfo. BepInEx treats the new GUID as a different plugin and generates a fresh Odin_Sons.ServerInfo.cfg; the old Maddy.Publicweblink.cfg is not migrated.

[1.6.0] - 2026-09-12

Added

  • MetadataSources config ([Mods] section) — a comma-separated order of precedence (Manifest, Assembly) admins can set to control where a mod's description/websiteUrl/dependencies come from, down to disabling one source entirely or reversing the default priority.

[1.5.0] - 2026-09-12

Added

  • LogLevel config ([Logging] section) — a BepInEx LogLevel flags value controlling which message levels this plugin writes to the log, defaulting to Error, Warning; add Info to also get startup/diagnostic messages.
  • Mods without a manifest.json now fall back to whatever their own assembly carries for description (AssemblyDescription), websiteUrl (an embedded RepositoryUrl), and dependencies (their BepInEx dependency GUIDs), instead of always returning null.

Changed

  • Documented that the plugin works fully without SteamApiKey set — players is still returned, just without AvatarUrl.

Fixed

  • The "SteamApiKey is not set" warning is now logged once instead of on every refresh.

[1.4.0] - 2026-09-12

Added

  • Numeric settings (Port, RequestTimeoutSeconds, CacheIntervalSeconds, AvatarCacheMinutes) now declare an acceptable range, so BepInEx clamps an out-of-range value in the .cfg file instead of accepting it as-is, and config-editor mods render them as a bounded slider.

Changed

  • Config settings split into [Server], [Cache], and [Steam] sections instead of one flat [General].
  • Documented in README: how the avatar cache is keyed and expires, and that installing is just "drop the DLL" — BepInEx generates the config file with defaults on first run.

[1.3.1] - 2026-09-12

Changed

  • The Windows URL-ACL access-denied message no longer suggests user=Everyone as the primary fix — it now points at finding and using the actual account running the server (whoami), with Everyone documented as a fallback rather than the default: it lets any local account on the machine claim the same URL reservation, not just the one that needs it.

[1.3.0] - 2026-09-12

Added

  • ServerInfoPath config option — the endpoint path no longer has to be /serverinfo.
  • CORS support (AllowedOrigin config, * by default) so a status page on another domain can call the endpoint directly from the browser.
  • A startup log line confirming the port and path the server is listening on.
  • Startup now detects a Windows URL-ACL access-denied error specifically and logs the exact netsh command to fix it.

Changed

  • Errors while handling a request are now logged server-side (with the full exception), not just returned in the response body.

[1.2.0] - 2026-09-12

Added

  • Avatar URLs are cached (AvatarCacheMinutes config, default 60) and pruned once entries go stale, instead of being fetched fresh on every refresh for every connected player.

Changed

  • Steam avatar lookups for all connected players are batched into a single GetPlayerSummaries call instead of one request per player.
  • CacheIntervalSeconds and RequestTimeoutSeconds are now configurable instead of hardcoded.
  • Code reorganized from a single file into GameReflection.cs, GameServerInfoProvider.cs, PackageManifestReader.cs, SteamAvatarService.cs, and Models.cs.
  • Renamed internal Thunderstore* types/methods to be format-agnostic (PackageManifest, ParseNamespace) — Hexium uses the same namespace/name convention, so nothing here was Thunderstore-specific.

Security

  • Steam avatar requests now use HTTPS instead of HTTP.

[1.1.1] - 2026-09-12

Fixed

  • namespace coming back null for mods whose package folder has no version suffix (some mod managers update packages in place).

[1.1.0] - 2026-09-12

Changed

  • mods in /serverinfo changed from a plain string array to objects enriched with each mod's own manifest.json: description, websiteUrl, dependencies, namespace, packageName.
  • Build no longer requires a local Valheim or BepInEx install — moved to NuGet-only dependencies (BepInEx.Core, UnityEngine.Modules), with ZNet/Peer access rewritten to use reflection instead of a direct assembly reference.

Removed

  • The unused Harmony dependency.