1. GETTING STARTED & COMPILATION
K.E.Y.S.T.O.N.E. consists of three crates inside the workspace: crates/core (process spawners, PTY, event loop, resource limits), crates/daemon (node REST/WS server, filesystem, embedded Lua 5.4), and crates/web (central panel, SQLite auth, remote manager, proxy).
1.1 Building From Source
Requires Rust (Cargo) and a C toolchain (for vendored Lua 5.4). Node.js is required to build the Svelte 5 + Vite frontend.
# Build daemon binary only cargo build --release --features demon --bin daemon # Build panel binary only cargo build --release --features web --bin panel # Build both binaries (full = demon + web) cargo build --release --features full # Build Web UI (outputs to webui/, served automatically by panel) cd webui-src && npm install && npm run build
1.2 Bootstrap & Launch
The panel will not start unless at least one active administrator exists in its SQLite database. Passwords must be at least 12 UTF-8 bytes and are hashed with Argon2id.
# 1. Start the daemon cp daemon.toml.example daemon.toml ./target/release/daemon daemon.toml # 2. Configure panel & bootstrap administrator cp panel.toml.example panel.toml cp remotes.toml.example remotes.toml ./target/release/panel account bootstrap --username admin # 3. Start panel (default http://127.0.0.1:24000) ./target/release/panel
2. CONFIGURATION FILES & FLAGS
2.1 Daemon Configuration (daemon.toml)
name = "daemon-1" listen = "127.0.0.1:24444" token = "" # empty token is allowed ONLY on loopback fs_root = "." # all API paths and instance cwd resolve inside this root data_dir = "./data" # stores instance-<uuid>.json records # cgroup_root = "/sys/fs/cgroup/keystone" max_file_bytes = 1073741824 preset_http_allowed_origins = ["https://api.papermc.io"]
| Key | Default | Description |
|---|---|---|
name |
"daemon-1" |
Node name reported in logs and diagnostics. |
listen |
"127.0.0.1:24444" |
Bind address. Non-loopback bind requires a non-empty token. |
token |
"" |
Bearer token checked via Authorization: Bearer <token>. Query-string tokens are not supported. |
fs_root |
"." |
Sandbox root directory for all file API calls and instance working directories. |
data_dir |
"./data" |
Directory where instance-<uuid>.json state files are persisted atomically. |
cgroup_root |
auto | Optional Linux cgroup v2 subtree path (defaults to <cgroup2 mount>/keystone). |
max_file_bytes |
1073741824 (1 GiB) |
Maximum file size accepted by streaming upload and rest.download. |
preset_http_allowed_origins |
[] |
Exact scheme://host[:port] origins allowed for Lua rest.request and rest.download. |
2.2 Panel Configuration (panel.toml) & Environment
listen = "127.0.0.1:24000" database = "panel.db" locales_dir = "locales" presets_dir = "presets" secure_cookies = false # set true when publishing panel over HTTPS
| Env / Flag | Purpose |
|---|---|
PANEL_CONFIG |
Path to panel config file (default: panel.toml). |
PANEL_LISTEN |
Overrides listen address from panel.toml. |
PANEL_REMOTES |
Path to remotes list file (default: remotes.toml). |
--allow-insecure-remotes / PANEL_ALLOW_INSECURE_REMOTES=1 |
Explicit testing flag: permits plain HTTP remote URLs outside loopback. |
--allow-invalid-certs / PANEL_ALLOW_INVALID_CERTS=1 |
Explicit testing flag: accepts self-signed TLS certificates for HTTPS REST and WSS terminal. |
2.3 Remote Daemons (remotes.toml)
By default, plain http:// is allowed only for loopback addresses (127.0.0.1, ::1, localhost). Remote nodes over the network require https:// unless --allow-insecure-remotes is enabled.
[[remotes]] id = "local" name = "Local daemon" url = "http://127.0.0.1:24444" token = "" [[remotes]] id = "node2" name = "Production Node EU" url = "https://node2.example:24444" token = "super-secret-bearer-token"
3. AUTHENTICATION, RBAC GRANTS & FLUENT I18N
Login establishes a 12-hour HttpOnly; SameSite=Strict cookie session (keystone_session). Every state-changing request (POST, PUT, PATCH, DELETE) additionally verifies both the X-CSRF-Token header and the HTTP Origin header.
Role-Based Grants (Restricted Users)
Administrators manage accounts in the Account tab. A restricted user sees only explicitly granted resources. For an allowed instance, a restricted user can start and stop (and view read-only installation terminals), while configuration changes, force-kill, and deletion remain admin-only.
| Grant Kind | Format | Effect |
|---|---|---|
remote |
<remote_id> (e.g. node1 or *) |
Allows viewing the daemon node and its metrics. |
instance |
<remote_id>:<uuid> |
Allows viewing, starting, and stopping the instance (automatically granted when a user instantiates an allowed preset). |
fs |
<remote_id>:<relative/path> |
Confines file manager operations and preset target cwd to this directory subtree. |
preset |
<preset_id> |
Allows previewing and instantiating the specified preset. |
Runtime Fluent Localization
The frontend contains no hardcoded translation bundles. The panel loads <locales_dir>/<code>/messages.ftl and meta.json, serves them via GET /api/locales/:code with ETag caching, and hot-reloads changes every 2 seconds without restarting.
4. INSTANCE CONFIGURATION & ENVIRONMENT
Each instance is stored on the daemon in <data_dir>/instance-<uuid>.json. Every configuration must include a resources object:
{
"resources": {
"cpu_percent": 150,
"cpu_affinity": [0, 2],
"memory_bytes": 2147483648
},
"nickname": "my-server",
"start_command": "java -jar server.jar nogui",
"stop_command": "stop",
"stop_timeout": 30,
"cwd": "servers/my-server",
"ie": "utf-8",
"oe": "utf-8",
"pty": true,
"cols": 120,
"rows": 30,
"auto_start": false,
"auto_restart": true,
"max_restarts": -1,
"tags": ["minecraft"],
"process_type": "general",
"env": {
"JAVA_HOME": "/opt/java"
}
}
| Field | Constraints | Description |
|---|---|---|
resources |
object (required) | cpu_percent (int or null), cpu_affinity (int array or null), memory_bytes (int or null). |
nickname |
1..128 bytes | Display name shown on instance cards. |
start_command |
1..16384 bytes | Command line parsed with POSIX quoting on Unix or native command splitting on Windows. |
stop_command |
0..16384 bytes | Text written to stdin on stop, or ^C to send SIGINT / CTRL_C_EVENT. |
stop_timeout |
0..300 seconds | Grace period before escalating to SIGTERM and SIGKILL. |
cwd |
1..4096 bytes | Working directory relative to daemon fs_root. |
ie / oe |
encoding label | Input/output encodings via encoding_rs (e.g. utf-8, cp866, windows-1251). |
pty, cols, rows |
bool, 20..1000, 5..1000 | Enable pseudo-terminal and initial terminal dimensions. |
auto_start / auto_restart |
bool / bool + max_restarts (≥ -1) |
Auto-start on daemon boot or after preset install; auto-restart on crash with exponential backoff (resets counter after 60s of stable uptime). |
env |
up to 256 vars | Custom environment variables. Prefix KEYSTONE_ is reserved. Daemon automatically injects KEYSTONE_INSTANCE_NAME, KEYSTONE_INSTANCE_UUID, KEYSTONE_START_COUNT, and KEYSTONE_RESTART_COUNT. |
5. NATIVE OS RESOURCE LIMITS
K.E.Y.S.T.O.N.E. enforces CPU and memory limits directly via OS kernel primitives without Docker. In resources, 100 in cpu_percent always means one full logical CPU regardless of host core count. Descendant processes share the instance's resource domain.
5.1 Platform Enforcement Matrix
| Platform | CPU Quota (%) | Logical CPUs (Affinity) | Memory Limit | Kernel Mechanism |
|---|---|---|---|---|
| Linux | Yes | Yes | Shared cgroup memory.max |
cgroups v2 (cpu, cpuset, memory) |
| Windows | Yes | Yes (processor groups) | Shared Job committed memory | Win32 Job Objects + native ConPTY |
| FreeBSD | With RACCT/RCTL & root | Yes | Shared resident memory (RCTL) | Login class + RCTL + cpuset |
| NetBSD | Unsupported | Native inherited affinity | Unsupported | sched_setaffinity_np |
5.2 Linux cgroups v2 Delegation (Unprivileged / runit)
No systemd or D-Bus dependency is used. When running as root, the daemon automatically prepares its cgroup v2 subtree. For an unprivileged keystone service user, delegate a subtree with separate daemon and instances leaves:
echo '+cpu +cpuset +memory' > /sys/fs/cgroup/cgroup.subtree_control mkdir -p /sys/fs/cgroup/keystone/daemon /sys/fs/cgroup/keystone/instances echo '+cpu +cpuset +memory' > /sys/fs/cgroup/keystone/cgroup.subtree_control chown keystone:keystone /sys/fs/cgroup/keystone \ /sys/fs/cgroup/keystone/cgroup.procs \ /sys/fs/cgroup/keystone/cgroup.subtree_control \ /sys/fs/cgroup/keystone/instances \ /sys/fs/cgroup/keystone/instances/cgroup.procs \ /sys/fs/cgroup/keystone/instances/cgroup.subtree_control
6. HTTP & WEBSOCKET API REFERENCE
6.1 Daemon REST API (/api/v1)
Protected by Authorization: Bearer <token> header.
| Method | Endpoint | Description |
|---|---|---|
| GET | /system | OS, CPU architecture, and resource limit capabilities |
| GET | /metrics | Real-time global & per-core CPU % and RAM usage |
| GET | /instances | List all instances with status and installation state |
| POST | /instances | Create instance directly ({ "config": {...} }) |
| GET / PUT / DELETE | /instances/:uuid | Inspect, update config (when stopped), or delete instance |
| POST | /instances/:uuid/<start|stop|kill> | Lifecycle actions |
| GET / POST | /instances/:uuid/<output|input> | Fetch buffered stdout history or write to stdin |
| POST | /presets/<options|preview|instantiate> | Evaluate dynamic_enum options, preview built config, or start preset installation |
| POST | /instances/:uuid/retry-install | Retry a failed preset installation |
| GET | /statuses | Batch status map of all instances |
| GET | /terminal?instance=<uuid>&cols=&rows= | WebSocket terminal stream |
| GET / PUT | /fs/read?path=, /fs/write | Inline UTF-8 text editor API (up to 2 MiB) |
| GET / PUT | /fs/file?path= | Streaming binary file download and upload (up to max_file_bytes) |
| GET / POST / DELETE | /fs/list, /fs/mkdir, /fs/rename, /fs/delete | Directory listing, creation, renaming, and deletion |
6.2 Panel REST API (/api)
| Method | Endpoint | Description |
|---|---|---|
| POST / GET | /auth/login, /auth/me, /auth/logout | Session authentication & CSRF token retrieval |
| PATCH / POST | /account, /account/password | Change own locale or password |
| GET / POST / PATCH / DELETE | /admin/users, /admin/users/:id, /admin/users/:id/password | Admin user & RBAC grant management |
| GET / POST / DELETE | /remotes, /remotes/:id | Manage connected daemons |
| GET | /remotes/:id/<instances|metrics|system> | Query a specific daemon's instances, CPU/RAM metrics, or OS capabilities |
| GET / POST / PUT / DELETE | /instances, /create?remote=, /instances/:uuid?remote= | Aggregated instance list, direct creation, config update, deletion |
| POST | /proxy/:uuid/<start|stop|kill>?remote= | Proxy lifecycle command to daemon |
| GET / POST / DELETE | /presets, /admin/presets, /admin/presets/reload | List allowed presets, save/delete presets, reload disk catalog |
| POST | /presets/:id/fields/:field/options, /presets/:id/preview | Evaluate dynamic dropdowns or preview built config on target daemon |
| POST | /presets/:id/instantiate, /presets/:id/retry/:uuid | Create instance from preset or retry failed installation |
| GET / PUT / DELETE / POST | /locales, /locales/:code, /admin/locales/:code | Runtime Fluent translation catalogs |
6.3 Terminal WebSocket Protocol
Connect via subprotocol keystone to GET /api/terminal?remote=<id>&instance=<uuid>&cols=120&rows=30&readonly=false. Binary frames carry raw stdin/stdout bytes; JSON text frames carry control events:
// Client -> Server (resize PTY)
{ "resize": [120, 30] }
// Server -> Client (lifecycle events)
{ "event": "opened" }
{ "event": "stopped", "exit_code": 0 }
{ "event": "failure", "error": "..." }
7. FAQ
On startup, the daemon marks any interrupted installing instance as failed. Clicking Retry in the panel re-sends the latest preset definition and values from the panel to the daemon.
Every daemon enforces an explicit allowlist in daemon.toml via preset_http_allowed_origins = ["https://api.papermc.io"]. Add the exact scheme, host, and port, then restart the daemon.
Set the RUST_LOG environment variable, e.g. RUST_LOG=debug ./daemon or RUST_LOG=info,keystone_web=trace ./panel.