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.

MethodEndpointDescription
GET/systemOS, CPU architecture, and resource limit capabilities
GET/metricsReal-time global & per-core CPU % and RAM usage
GET/instancesList all instances with status and installation state
POST/instancesCreate instance directly ({ "config": {...} })
GET / PUT / DELETE/instances/:uuidInspect, 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-installRetry a failed preset installation
GET/statusesBatch status map of all instances
GET/terminal?instance=<uuid>&cols=&rows=WebSocket terminal stream
GET / PUT/fs/read?path=, /fs/writeInline 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/deleteDirectory listing, creation, renaming, and deletion

6.2 Panel REST API (/api)

MethodEndpointDescription
POST / GET/auth/login, /auth/me, /auth/logoutSession authentication & CSRF token retrieval
PATCH / POST/account, /account/passwordChange own locale or password
GET / POST / PATCH / DELETE/admin/users, /admin/users/:id, /admin/users/:id/passwordAdmin user & RBAC grant management
GET / POST / DELETE/remotes, /remotes/:idManage 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/reloadList allowed presets, save/delete presets, reload disk catalog
POST/presets/:id/fields/:field/options, /presets/:id/previewEvaluate dynamic dropdowns or preview built config on target daemon
POST/presets/:id/instantiate, /presets/:id/retry/:uuidCreate instance from preset or retry failed installation
GET / PUT / DELETE / POST/locales, /locales/:code, /admin/locales/:codeRuntime 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

What happens if a daemon restarts while a preset is installing?

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.

Why does Lua rest.request fail with 'REST origin is not allowed'?

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.

How do I enable debug or trace logging?

Set the RUST_LOG environment variable, e.g. RUST_LOG=debug ./daemon or RUST_LOG=info,keystone_web=trace ./panel.