ValhSync

Run a modded Valheim server, and hand your players a launcher that keeps up with it. Two windows: yours starts and stops the dedicated server, installs and retires mods, keeps the admin and ban lists, writes the patch note and backs up the world. Theirs is one button.

Download ValhSync Windows and Linux · the admin's archive holds both programs and the guides; the player's holds the launcher and nothing else.

Source on GitHub · All releases · MIT or Apache-2.0

This is an early version. 0.0.3 is the third release there has ever been. One server has actually run it — a Windows machine, a dedicated server beside it, a handful of players — and the whole chain works there. Everything else is tested but has not met a real server: Linux, hosted providers, Proton, any setup that is not the one above.

So: back up the server's BepInEx folder before pointing ValhSync at one that matters, and expect to find things. Finding them is what this stage is for — say so on the repository when you do.

Built with AI assistance. All of it — the two programs, the tests, this documentation — was written with an AI assistant (Claude), directed and reviewed by a human. That is said here plainly because you should know what you are about to run.

§1What it is

Two programs. The first is where a modded server is run from: start and stop, install a mod by dropping it on the window, turn one off or take it out, admins and bans, the patch note, a copy of the world before a change, and the pack published signed at the end of it. The second is what the players get, and it is one button.

The reason both exist is the same one.

Valheim has no native way to distribute mods. There is no Steam Workshop for it, even in 1.0, and Iron Gate does not officially support modding: everything goes through BepInEx and third-party managers. On a modded server that breaks in several ways at once.

  • The version handshake. Mods built on ServerSync or Jotunn check at connection time that the client has the same mod at the same version. If it does not, the client is dropped, and the game shows one generic message.
  • Mods that refuse each other. One leftover plugin on a player's machine can stop another from loading at all, and the server rejects them for it.
  • Scattered sources. Some authors publish outside Thunderstore; a manager that does not read that source hands a player a version from before the last game update.

ValhSync makes the server the single source of truth. The admin's machine already has the exact set of mods the server runs — that is what it is running. ValhSync publishes it, signed, and the launcher makes the player's game folder match before Valheim starts.

No installer. Two executables, no registry, no service to register, no account to create. Unpack and run.

§2How it works

 Server machine                              Player's PC
 ┌────────────────────────────┐             ┌──────────────────────────────┐
 │ Valheim dedicated server   │             │ valhsync (launcher)          │
 │  └ BepInEx/ (server mods)  │   HTTP/TCP  │  1. GET /manifest.json + sig │
 │                            │ ◀─────────▶ │  2. verify Ed25519 signature │
 │ valhsync-server            │             │  3. compare with local files │
 │  ├ scans the pack          │             │  4. GET /files/<blake3>       │
 │  ├ signs the manifest      │             │  5. apply, journalled        │
 │  └ content-addressed store │             │  6. start Valheim via Steam  │
 └────────────────────────────┘             └──────────────────────────────┘

A signed manifest

The server publishes a JSON manifest and a detached Ed25519 signature over the exact bytes. The launcher pins the server's public key when the server is added, and verifies the signature before parsing anything. A manifest that does not verify is never read.

Content-addressed files

Every file is fetched by its BLAKE3 digest and checked after download. A URL can only ever name a hash, never a path, so there is no path for a request to traverse. Nothing is written until every file has arrived and verified.

One port, and it is already open

The live publisher serves on the game's own port in TCP. Valheim uses that port in UDP only, so the router rule an admin already has covers both. ValhSync never asks for a port the game does not already use. Admins who prefer to open nothing export a static folder instead and upload it anywhere.

§3The admin's window

Run valhsync-server with no arguments and it opens a window. Four tabs.

The ValhSync server window on its Server tab: the game
      server online with Stop and Restart, publishing, the public address, and the console
      below.
The admin's window. The dedicated server is started, stopped and restarted from here, and the pack goes online behind it.
The Mods tab: a drop zone for installing a mod, then one row
      per mod with sent-to-players or server-only, and buttons to disable or remove it.
Mods are installed by dropping them on the window. Each one either travels to players or stays on the server, and can be turned off or taken out from its own row.

Server

  • Start, restart and stop the dedicated server. Stopping sends Ctrl+C to its console, so Valheim writes the world before it exits; the process is never killed. A server that exits without being asked to can be brought back on its own — off by default, and bounded, because the failure this exists for is also the failure that loops.
  • Back up the world before a change: both halves together, beside worlds_local and never inside it, with a warning that a copy taken while the server runs may be torn.
  • Players online, join code, Valheim version, public address, process id.
  • Its log, followed live, and below it a resizable console holding the lines the server prints to its own — a handful among tens of thousands.
  • A warning when Steam has an update waiting for the dedicated server: the game and the server are separate Steam applications, and one updating without the other refuses every connection.

Mods

  • One row per mod found in the server's BepInEx folder.
  • Each is either sent to players or server only: admin tools and Discord bridges have no reason to travel.
  • Drop a zip from anywhere, a mod folder or a bare .dll on the window to install it; an update replaces the version that was there.
The Players tab: the admin list and the banned list, each
      id shown with the name the server's log recorded for it, and a field to add another.
Admins, bans and the permitted list, written to the three files Valheim reads. The names come from the server's own log, because seventeen digits are no way to decide who to ban.

Players

Admins, bans and the permitted list, from the window or from the console's prompt: ban <id>, unban, admin, permit, and banned to see a list. These write the three files Iron Gate documents, which is the only channel a dedicated server has from outside the game: it does not read its console, whatever its start-up banner claims, and Valheim has no RCON. Kicking somebody who is connected right now, and saving on demand, are an admin pressing F5 in the game — typing those here says so instead of failing quietly.

Patch notes

Its own tab, published signed with the pack. The list of mods added, updated and removed writes itself from the plan, so no version number is ever retyped; what you add is the half a diff cannot produce — that a mod resets its own config, that this update only matters to the people who were crashing on the boat. Players read it before the sync if they want to, and it is still there afterwards — nothing waits on them agreeing to anything.

Announcements

A Discord webhook, and every published pack is posted to that channel with what changed and whatever the admin wrote. It is absent by default, and absent means off: ValhSync contacts no third party unless somebody pasted an address in.

Settings

Where the dedicated server lives, the name and address players see, publishing — a static folder to upload, or the live server — and the address and key fingerprint to hand out, with an invite code beside them for admins who prefer one. Everything writes itself: there is no Save button, because a setting that is on screen and not on disk is a server nobody can reach.

Publishing follows the game server. Bring the game up, however you bring it up, and the pack goes online behind it.

The window speaks eight languages — English, French, German, Spanish, Italian, Polish, Portuguese, Russian — chosen from the menu at the top right and remembered. English is the default; the language the system asks for is offered first.

The command line does all of the same things, and is what a service unit runs — which is the path on a headless Linux box. Setting the whole thing up, on either platform, is the server guide.

§4The player's launcher

The launcher: one server, what it is about to install, the
      admin's note, and the mods the server runs.
One window, one button, and whatever the admin wrote.

One window, one button. Press Play: if anything has changed it is installed first, then Valheim starts through Steam.

A server is added by its address. The same ip:port that goes into Valheim's own join box — 203.0.113.10:2456 — pasted into the launcher. That is the ordinary way in, and the only thing a player needs from the admin. The launcher fetches the server's key and shows its fingerprint, which is what the admin announces once so it can be compared.
  • An invite code does the same thingvalhsync1:…, carrying the key inside it, so there is no fingerprint to compare. Handy for a file shipped beside the executable; the address is what most people will paste. Several servers can sit side by side.
  • Patch notes before the install, not after. Mod by mod and by name — added, updated, removed, computed from the pack itself — above whatever the admin wrote about the release. Readable before the sync and still readable after it.
  • Nothing is deleted. Files that get replaced go to a backup. Mods the player installed themselves that are not in the pack are set aside in a folder inside the game, never removed. The last sync can be rolled back exactly.
  • A repair pass re-verifies and re-fetches anything that no longer matches.
  • Play without mods renames winhttp.dll rather than uninstalling anything, and the next sync puts it back.
Nothing is applied half-way. Everything is downloaded and verified first, every change is journalled with the previous bytes stashed, and a failure rolls back on its own. A crash mid-sync leaves a working game folder.

§5Updating the launcher

A server can offer players a newer ValhSync. The offer is a document signed by the same key they already pinned for the mod pack, naming a build by its BLAKE3 digest; the launcher checks the signature before reading a field of it, checks the digest before installing a byte of it, and refuses anything that is not strictly newer or not built for that machine.

The prompt names the server and its key fingerprint, because that is what the player is being asked to trust — see §6. Declining changes nothing: syncing and playing carry on, and releases can always be fetched from the repository instead.

§6Security, honestly

The full threat model is in SECURITY.md. The shape of it:

ThreatWhat stops it
Manifest forged or altered in transitEd25519 signature over the exact bytes, verified before parsing, key pinned at import
File altered in transit or on the serverBLAKE3 checked after download; nothing applied until everything verifies
Replay of an old, validly signed manifestThe launcher remembers the newest it has seen and refuses an older one
Path traversal, drive letters, UNC, reserved device namesStrict syntax check plus an allow-list of roots; one bad entry rejects the whole manifest
Symlinks or junctions in the game folderNever followed, checked at plan time and again at apply time
Names that render as something other than what they areControl characters, bidirectional overrides and zero-width joiners all refused
Server-side path access through a URLFiles served by digest only, from a content-addressed store; no listing, no write route
The admin is trusted. A plugin is arbitrary code with the player's rights. Importing an invite means trusting that admin, exactly as accepting a zip of mods from them does — and since the update channel, it also means they can offer a replacement launcher, which runs whether or not Valheim is started. ValhSync does not try to protect against a malicious admin, and says so rather than implying otherwise.

The transport is plain HTTP by default. Confidentiality is not a goal — mod files are public — and integrity does not depend on the transport. An admin who wants TLS puts a reverse proxy in front; the launcher accepts https:// and verifies certificates against bundled roots.

Builds are not signed with a code-signing certificate yet, so Windows SmartScreen warns on first run and Defender's machine-learning classifier occasionally reports a fresh build. Release hashes are published to be checked in the meantime. A certificate has been applied for, the release workflow already submits the Windows binaries for signature, and the code signing policy says who can cause a signature to happen and what one would mean.

§7Licences

ValhSync

MIT OR Apache-2.0, at your option — the Rust ecosystem's usual pair. Both texts ship with the source and in every release archive (LICENSE-MIT, LICENSE-APACHE). Written from scratch; not a fork of any existing mod manager.

Typefaces

Both windows embed two faces, under the SIL Open Font License 1.1, whose texts travel with every release:

  • Cinzel by Natanael Gama — headings, buttons and labels. A Roman inscriptional face: the closest thing to letters carved in stone.
  • Source Serif 4 by Frank Grießhammer — running text and paths.

Dependencies

464 crates in the full graph, permissively licensed throughout. No GPL, AGPL or SSPL, and nothing unlicensed. cargo deny enforces the allow-list in deny.toml on every CI run, so this cannot drift unnoticed.

LicenceCratesNote
MIT and/or Apache-2.0~360the overwhelming majority, most offering both
Unicode-3.018Unicode character tables
Zlib, BSD-2/3-Clause, ISC, BSL-1.0, 0BSD~25permissive, usually as one option among several
CC0-1.0, CDLA-Permissive-2.04public-domain dedication and permissive data licence
MPL-2.01option-ext, reached through directories. File-scoped copyleft: modifying that crate's own files means publishing those files. Linking it does not affect ValhSync's licence.

Two crates offer MIT OR Apache-2.0 OR LGPL-2.1-or-later; the permissive option is taken. The window toolkit bundles a fallback font of its own under OFL-1.1 and the Ubuntu Font Licence.

Mods are not ValhSync's to license. Serving DLLs from your own server is ordinary between friends, but some mod authors forbid redistribution. Check what you publish.

§8What has been tested

Worth stating plainly, because the answer is narrower than the code's reach. One platform has been verified end to end, including across several players' own machines. Everything else either compiles and passes its tests, or is untried.

PlatformStatus
Windows 11, x86_64Verified end to end, in one configuration: a PC running the Valheim dedicated server (Steam app 896660) with the publisher beside it, serving on the game's port. The launcher fetched the pack over that machine's public address, applied it, and Steam started Valheim into the server.
Other machines as playersVerified with 0.0.1. Several players ran the launcher on their own PCs — their own Valheim installs, their own game folders, their own pre-existing mods — synced from the publisher and joined. That is the run that exercises quarantine, backups and the root allow-list against installations nobody arranged in advance.
This release across machinesNot yet. The multi-player run above was 0.0.1; the release notes and the update channel have only been exercised between a publisher and a launcher on one PC.
Linux, x86_64Builds and passes the suite in CI — since this repository's first run, and not before it. It used to say “on every commit”, which was not true: there was no CI until the project was published, and its first look found two things that had never compiled on Linux at all. What passes there now includes the end-to-end tests, which stand a real publisher on a real TCP listener and drive the launcher's real engine against it: a pack installed, replaced, rolled back, an older manifest refused as a replay, an update offer verified and written. What has never happened on Linux is a window being opened or Steam being asked to start the game.
Proton / Steam DeckUntested. The launch-option lines the launcher shows come from the BepInEx documentation, not from a machine here.
Dedicated server in DockerUntested, and partly unsupported by design: the publisher finds the game server by looking at processes, so it cannot see one in another container. Publishing a pack from a mounted volume should work; starting, stopping and the console will not.
Hosted servers (G-Portal, Nitrado…)The layout has a test — publishing with no local server root, from a copy of the pack, with game_address pointed at the host — but no real hosting provider has been on the other end of it.
macOSNot built, not tested.
If you try one of these, a report either way is worth more than any amount of reasoning about it from here. Open an issue with what you ran and what happened.

§9Building it

Rust stable, edition 2024. No build container, no code generation step, no vendored C.

cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo run -p valhsync-server     # the admin's window
cargo run -p valhsync            # the player's launcher

On Linux the window needs the usual X11/Wayland development packages. Release archives are built by scripts/package.ps1, which passes --remap-path-prefix so no build machine's paths travel inside the binaries, and refuses to package a build that was made without it.

The workspace is four crates: valhsync-core (pure logic over bytes and the local filesystem, no network, where most of the tests live), valhsync-server, valhsync (CLI and window in one executable), and valhsync-ui (the shared theme).