In Stage 3a, Waystone learned to start and stop a Minecraft server. There was a catch: I had to set that server up by hand first, including its user, its folder and its systemd unit. As of Stage 3b, that's no longer true. One request now gives a game server everything it needs to exist on a machine.
What happens when you provision a server
The agent sends one small request: a server ID, the name of a runtime (java25) and a heap size. That's all it can send. It can't pass paths, user names or unit names, because Waystone works all of those out itself from the ID. On the other side:
- The server gets its own Linux account. It's a system account with no home directory and no login shell, named after a hash of the server ID. Every game server runs as a different user, so one server can't read another's files.
- Waystone writes a systemd unit from a template. The template hard-codes the sandbox: no capabilities, a read-only system, private
/tmpand devices, and a memory limit of the heap plus 1 GB. - systemd creates the server's data folder, owned by that account and closed to everyone else (mode 0750). Waystone's own code never changes file ownership.
Run the same request again and nothing changes. The answer is simply changed: false. And Waystone never takes over anything it didn't create: an account or unit file that looks like someone else's is refused, not overwritten.
Two privileged services, each with one job
Creating Linux accounts needs root, and the obvious shortcut would have been to give the Stage 3a helper more power. We didn't. Instead:
- The runtime helper is unchanged. It still only starts, stops and inspects servers, and it can't write a single file.
- A new provisioning service runs the same program in a different role. The only place it may write is the systemd unit directory.
- Accounts are created by the system's own
useradd, in a short-lived sandboxed unit with no capabilities. It's the only process in the whole design that can write to/etc, and only for that one command.
Each service refuses the other's job. We checked that on the machine: the runtime helper rejects provisioning, the provisioner rejects start and stop, and both refuse root itself. Both score 1.3 ("OK") in systemd-analyze security.
We're also honest about the limits. Anything that can write systemd units is effectively root on that machine, so these sandboxes exist to contain mistakes, not to stop an attacker who has already broken in. The real boundary sits in front of them: the agent isn't root, only the agent can reach these services, and the requests are small and strictly typed.

It plays
To close the stage, I gave a freshly provisioned server the Minecraft 26.3 jar and EULA. It started in 2.4 seconds, ran as its own account under systemd rather than as a child of the agent, and I joined it from my client.
We also settled an open question from Stage 2. A normal stop does save the world. I broke some blocks, stopped the server well before any autosave could run, and they were still broken after the restart. Minecraft just doesn't say so in its log.
What's next
For this test I copied the jar in by hand. Stage 3c removes that step: a small worker, running as the server's own user, will download and verify the jar, accept the EULA and write the settings. After that, a game server goes from nothing to joinable without anyone touching the machine.