Running a server

The resource-pack host

On this page

rustmc can serve resource packs over HTTP itself, so you or a plugin can offer players a pack without a separate web host. A Java client downloads a pack from the URL it is offered and checks the download against a SHA-1 hash. The pack host serves that URL and works out the hash for you.

The pack host only serves packs. Offering one to players is done by a plugin, with push_pack.

Turning it on

The pack host is off by default. In rustmc.yml:

pack-host:
  enabled: true
  host: 127.0.0.1
  port: 25586
  url: ""
  directory: packs
  max-pack-mib: 250

The settings are read at startup. When the server starts with the pack host on, it creates the packs directory if it is missing and prints

Pack host listening on http://127.0.0.1:25586, offering packs as http://127.0.0.1:25586/

If the pack host cannot start (the port is taken, say), the server prints pack host: and the reason, and carries on without it.

Settings

KeyDefaultWhat it does
pack-host.enabledfalseServes resource packs over HTTP from this server.
pack-host.host127.0.0.1The address the pack host binds. The default takes connections from this machine only.
pack-host.port25586The TCP port. 0 binds whatever port is free and prints it at startup.
pack-host.url""The address clients are told to fetch packs from, up to the path. Empty uses http://<bound address>.
pack-host.directorypacksThe directory whose .zip files are served, relative to the server's own files or absolute.
pack-host.max-pack-mib250The largest pack served or hosted, in MiB, from 1 to 1024. 250 is the most a Java client downloads.

Reaching it from other machines

A client fetches the pack itself, so players must be able to reach the URL they are told. 127.0.0.1 reaches only the machine the server runs on, which is fine for testing and nothing else. For a public server:

  • bind an address players can reach (host: 0.0.0.0, and open the port in your firewall), or keep it on localhost and put a reverse proxy or tunnel in front of it; and
  • set url to the address players use, up to the path: http://play.example.com:25586, or https://packs.example.com behind a proxy. A trailing / is ignored. Plugins are handed URLs built on it.

How it behaves

What is served

PathWhat
/<name>.zipa .zip directly inside pack-host.directory, put there by you
/plugins/<plugin>/<name>.zipa pack a plugin hosted with host_pack

Nothing else is served:

  • Only GET is answered. Any other method, HEAD included, is 405.
  • There is no directory listing, and nothing in a subdirectory of pack-host.directory is served.
  • Each part of a path must be letters, digits, ., - and _, at most 128 characters, and must not start with a dot. Anything else (.., %2e, a backslash, a drive letter) is 400 before any file is opened. A query string is ignored.
  • A missing pack is 404, and a pack larger than max-pack-mib is 403.
  • A pack that exists but cannot be read is 500, and the server prints why.

Each pack is sent as application/zip, with its SHA-1 as the ETag. To read a pack's hash, make a GET and look at the headers:

curl -s -D - -o /dev/null http://127.0.0.1:25586/lobby.zip

or work the hash out yourself with sha1sum lobby.zip.

Changing a pack

Each pack's bytes and hash are read once and kept in memory, along with the file's length and modification time. When either changes, the file is read again, so you can replace a pack without a restart, and a hundred players downloading the same pack share one copy of it.

A client keeps a pack by its hash. A changed pack has a new hash, so it has to be offered to players again with the new hash before they pick it up.

What a plugin hosts at /plugins/... is remembered in memory only. After a restart those URLs answer 404 until the plugin hosts its packs again. Packs in pack-host.directory are served whether or not a plugin has asked for them.

Limits

  • At most 64 connections are served at once. The next one is answered 503 and closed.
  • A request must arrive within 10 seconds of connecting, and its head must be at most 8 KiB (larger is 431).
  • An answer must be taken within 60 seconds plus one second per 256 KiB, which is about 17 minutes more for a 250 MiB pack. A client slower than that, or one that never reads, is dropped.
  • Each connection carries one request and is then closed.
  • The pack host speaks plain HTTP. For HTTPS, put a proxy in front of it and set url to the proxy's address.

Security and privacy

  • Bound to 127.0.0.1 by default, the pack host is not reachable from other machines until you change host.
  • Anyone who can reach the pack host can download any pack it serves. There is no sign-in, so do not put anything in pack-host.directory you would not hand to a player.
  • A plugin can only host packs from its own plugins/<plugin>/ directory. A path that would leave it (.., an absolute path, or a link or junction pointing outside) is refused.
  • The pack host keeps no record of who downloads what.

For plugin authors

host_pack hosts a pack and answers with the URL and SHA-1 that push_pack needs. It fails while pack-host.enabled is off or the pack host is not listening, so fall back (or tell the operator) when it returns an error.

use rustmc_plugin::pack::PackSource;

let pack = host.host_pack("myplugin", "main", PackSource::Bytes(&zip))?;
host.push_pack(player, PACK_ID, &pack.url, &pack.sha1, false);

name is the pack's name in its URL: up to 64 letters, digits, - and _, with or without a .zip on the end. The plugin named is always the one asking: a Wasm plugin's is its manifest's name, never one it sends. There are three sources:

SourceServed atNotes
Bytes(&zip)/plugins/<plugin>/<name>.zipkept in memory; hosting the same name again replaces it
File("packs/main.zip")/plugins/<plugin>/<name>.zipa .zip under the plugin's own plugins/<plugin>/ directory, by a relative path with / between parts; read again when it changes
Directory/<name>.zipa pack the operator put in pack-host.directory, found by name, not copied

A pack larger than max-pack-mib is refused.

Packs stack on the client: pushing a second pack does not replace the first. To replace a pack, push it under the same id, or take the old one off with pop_pack. See resource packs for all three calls.

From a Wasm plugin

{"call": "host_pack", "name": "main", "base64": "UEsDBBQ..."}
{"call": "host_pack", "name": "main", "file": "packs/main.zip"}
{"call": "host_pack", "name": "lobby", "directory": true}

Give exactly one of base64, file or directory: true. The answer is {"ok": {"url": "...", "sha1": "..."}}. One transfer carries at most 4 MiB (limits), so bytes sent as base64 fit a pack of about 3 MiB. A bigger pack is written to the plugin's own directory (by its build, or by the operator) and hosted with file. The Rust guest SDK has host::host_pack for all three.

From docs/pack-host.md in the RustMC repository at bbd81209ad.