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
| Key | Default | What it does |
|---|---|---|
pack-host.enabled | false | Serves resource packs over HTTP from this server. |
pack-host.host | 127.0.0.1 | The address the pack host binds. The default takes connections from this machine only. |
pack-host.port | 25586 | The 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.directory | packs | The directory whose .zip files are served, relative to the server's own files or absolute. |
pack-host.max-pack-mib | 250 | The 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
urlto the address players use, up to the path:http://play.example.com:25586, orhttps://packs.example.combehind a proxy. A trailing/is ignored. Plugins are handed URLs built on it.
How it behaves
What is served
| Path | What |
|---|---|
/<name>.zip | a .zip directly inside pack-host.directory, put there by you |
/plugins/<plugin>/<name>.zip | a pack a plugin hosted with host_pack |
Nothing else is served:
- Only
GETis answered. Any other method,HEADincluded, is405. - There is no directory listing, and nothing in a subdirectory of
pack-host.directoryis 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) is400before any file is opened. A query string is ignored. - A missing pack is
404, and a pack larger thanmax-pack-mibis403. - 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
503and 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
urlto the proxy's address.
Security and privacy
- Bound to
127.0.0.1by default, the pack host is not reachable from other machines until you changehost. - 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.directoryyou 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:
| Source | Served at | Notes |
|---|---|---|
Bytes(&zip) | /plugins/<plugin>/<name>.zip | kept in memory; hosting the same name again replaces it |
File("packs/main.zip") | /plugins/<plugin>/<name>.zip | a .zip under the plugin's own plugins/<plugin>/ directory, by a relative path with / between parts; read again when it changes |
Directory | /<name>.zip | a 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.