Admin Guide

This guide covers the administrative aspects of running an UrsaMU server,
including user management, configuration, backups, and troubleshooting.

First-Run Setup

UrsaMU handles first-run setup automatically. This only needs to happen once.

How it works

Run the server for the first time:

deno task start

When the database is empty, the startup script pauses and prompts you:

No players found in the database.
Welcome! Let's set up your superuser account.

Enter email address: you@example.com
Enter username: Admin
Enter password: ••••••••

Superuser 'Admin' created successfully!

After setup completes, the Hub and Telnet sidecar start automatically.

Permission levels

UrsaMU uses a flag-based permission system:

Flag Level Who can set it Description
superuser 10 First-run prompt only Full server owner access
admin 9 Superuser (in-game) Full admin command access
wizard 9 Superuser (in-game) Same as admin — alternative role name
storyteller 8 Admin (in-game) Storytelling/moderation access
builder 7 Admin (in-game) Building permissions
player 1 Automatic on create Standard player

From inside the game, grant admin rights with:

@set TheirName=admin

The superuser flag cannot be granted via @set — it can only be created
through the first-run interactive prompt.

Non-interactive environments: If you run deno task start without a TTY,
the prompt is skipped. Run deno task server directly in an interactive
terminal to complete first-run setup, then switch back to deno task start
for normal operation.


User Management

Creating Admin Users

To grant admin privileges to an existing player, use the @set command as a
superuser or existing admin:

@set <username>=admin

Managing User Accounts

As an administrator, you can manage user accounts with these commands:

  • @newpassword <user>=<password> — Reset a user’s password directly
  • @resettoken <user> — Generate a one-time password-reset token (valid 1 hour);
    give the token to the player so they can reset via POST /api/v1/auth/reset-password
  • @boot <user> — Disconnect a player from the server
  • @toad <user> — Convert a player object into a regular thing (removes player
    flag and disconnects)
  • @chown <object>=<player> — Transfer ownership of an object to another player
  • @moniker <player>=<display name> — Set a player’s color-coded display name
    (admin or wizard required)

User Roles and Permissions

See the Permission levels table in the First-Run Setup
section above. In summary:

  • superuser (10) — created at first run, cannot be granted in-game
  • admin / wizard (9) — granted by a superuser with @set
  • storyteller (8), builder (7) — granted by an admin with @set
  • player (1) — automatic on character creation

To set a flag on a player:

@set <username>=<flag>

To remove a flag (prefix with !):

@set <username>=!<flag>

Bulletin Board Administration

Admins and wizards can create and destroy bulletin boards:

+bbcreate <name>[=<description>]     -- Create a board
+bbdestroy <board>                   -- Destroy a board and all its posts

Board names are slugified automatically (spaces become dashes). Example:

+bbcreate General=General discussion board
+bbcreate Staff=Staff-only discussion
+bbdestroy general

The REST API exposes full board management under /api/v1/boards (see
the REST API section below).

Jobs System

The jobs system lets players submit requests to staff. As staff you have
additional commands:

+job/assign <#>=<player>          -- Assign a job to a staff member
+job/status <#>=<status>          -- Set status (new/open/pending/in-progress/resolved/closed)
+job/priority <#>=<priority>      -- Set priority (low/normal/high/critical)
+job/complete <#>=<resolution>    -- Mark resolved with a resolution note
+job/reopen <#>                   -- Reopen a closed job
+job/staffnote <#>=<text>         -- Add a staff-only note (not visible to submitter)
+job/delete <#>                   -- Delete a job

Job statistics are available via the REST API at GET /api/v1/jobs/stats.

Channel Administration

Admins and wizards can create, configure, and destroy communication channels:

@chancreate <name>[=<header>]        -- Create a channel
@chancreate/hidden <name>            -- Create a hidden channel
@chancreate/lock <name>=<expr>       -- Create a channel with a lock
@chandestroy <name>                  -- Destroy a channel
@chanset <name>/header=<text>        -- Change a channel's header
@chanset <name>/lock=<expr>          -- Set a lock expression
@chanset <name>/hidden=true|false    -- Toggle channel visibility
@chanset <name>/masking=true|false   -- Toggle name masking

Master Room & Zones

Master Room

UrsaMU supports a single designated master room. Commands and $-pattern attributes set on objects in the master room are checked globally — any player anywhere on the game can trigger them, as if those objects were in the same room.

The master room ID is configured via game.masterRoom in config.json:

{
  "game": {
    "masterRoom": "1"
  }
}

ACONNECT and ADISCONNECT attributes fire on both the connected player’s own object and on objects in the master room, allowing global connection hooks without touching the player object.

Zone System

Zones allow a zone master object to share $-pattern commands across all objects assigned to it.

@zone <object>=<zone master>   -- assign object to a zone
@zone <object>=                -- clear zone assignment

Objects in a zone inherit $-pattern command dispatch from the zone master, making it easy to share a command set across many rooms or items without duplicating attributes on each one.

To see what zone an object belongs to, use examine <object> — the zone master dbref is listed in the output.


Rate Limiting

The server enforces a WebSocket command rate limit of 10 commands per second
per connection. Requests that exceed this are silently dropped and logged to
stderr:

[WS] Rate limit hit for socket <id> (cid: <player-id>)

This is a fixed limit defined in WebSocketService and cannot currently be
configured at runtime.

Server Configuration

Basic Configuration

Run the interactive configuration wizard:

deno task config

Key configuration areas:

  • Server ports (Hub WS: 4202, Hub HTTP: 4203, Telnet: 4201)
  • Game name and welcome messages
  • Starting room ID

Runtime Configuration (@site)

Admins and wizards can change a subset of server configuration values at runtime
without restarting:

@site server.name=My Awesome Game
@site game.loginMessage=Welcome back!
@site game.welcomeMessage=Welcome to the game!
@site server.banner=A roleplay game set in urban fantasy

Allowed keys: server.name, server.description, server.banner,
server.corsOrigins, server.maxConnections, game.maxPlayers,
game.description, game.loginMessage, game.welcomeMessage.

Attempts to set other keys (e.g. server.db, jwt.secret) are blocked and
logged to the security log.

Restarting and Shutting Down

From in-game (admin or wizard required):

@reboot      -- Gracefully restart the server
@shutdown    -- Shut down the server
@update      -- Pull latest code from git and restart (see below)

From the terminal, use Ctrl+C to stop a running process, then restart with:

deno task start     # Hub only (no watch)
deno task dev       # Hub + Telnet with file watching (development)
deno task daemon    # Background service with restart loop (production)

Hot-Reload (@reload)

Without restarting or disconnecting any players, admins and wizards can reload
individual parts of the running server:

@reload                    -- reload everything (config + text + commands + plugins)
@reload/config             -- reload config.json from disk only
@reload/text               -- reload text files (motd, etc.) only
@reload/cmds               -- reload native commands and system aliases only
@reload/plugins            -- hot-reload all installed plugins
@reload/plugin <name>      -- hot-reload one specific plugin by name

Example — reload just the jobs plugin after an update:

@reload/plugin jobs

The response shows each subsystem’s status. Partial failures are reported
inline so you can see which component (if any) didn’t reload cleanly.

@reload/plugin <name> is case-insensitive and matches against the plugin’s
registered name. To see what’s loaded: @reload/plugin (no name) prints
the list of currently loaded plugins.

System scripts (system/scripts/*.ts) are always executed live — the
engine reads and compiles them on each invocation — so they never need a
reload.

Updating from Git (@update)

Admins and wizards can update the running server from in-game without touching
the terminal:

@update              -- git pull from the default branch (origin/main)
@update main         -- pull a specific branch
@upgrade             -- alias for @update

The command:

  1. Broadcasts %chGame>%cn Updating from git... to all connected players
  2. Runs git pull origin <branch>
  3. Exits with code 75, which tells the daemon restart loop to reboot the server

Telnet connections survive the reboot — the Telnet sidecar stays up and
reconnects to the Hub automatically.

Daemon Restart Loop

When started via bash scripts/daemon.sh, the server runs inside a restart
loop (scripts/main-loop.sh) that watches the process exit code:

Exit code Meaning Action
75 Restart signal (@reboot, @update) Restart after delay
0 Clean shutdown (@shutdown) Stop — do not restart
Other Unexpected crash Stop — check logs

Rapid-restart protection — if the server exits in under 5 seconds the
restart delay doubles (1 s → 2 s → 4 s → … capped at 60 s). A stable
long-running restart resets the delay back to 1 second.

The deno child PID is written to .ursamu-deno.pid; the loop PID is in
.ursamu.pid. Use bash scripts/stop.sh to gracefully stop both.

Help Administration

The help command is provided by the help-plugin,
which aggregates entries from three sources in priority order:

Priority Source Description
100 Database Entries created with +help/set — override everything
50 Files Markdown files in ./help/ and plugin help dirs
10 Commands Inline help: fields from addCmd() registrations

In-game admin commands

+help/set <topic>=<text>   ← create or update a DB entry (Markdown supported)
+help/del <topic>          ← delete a DB entry
+help/reload               ← bust the file cache after adding/removing files

DB entries immediately override any file or command entry with the same slug.
Use +help/del to restore the underlying file entry.

REST API

GET    /api/v1/help              ← list all sections and topics
GET    /api/v1/help/:topic       ← fetch a topic (?format=md for raw Markdown)
POST   /api/v1/help/:topic       ← create/update (admin token required)
DELETE /api/v1/help/:topic       ← delete (admin token required)

Adding help files

Drop .md or .txt files in ./help/ (or a subdirectory) and run
+help/reload in-game. No restart needed. See Writing Help Files
for directory layout and Markdown conventions.


Wiki Administration

The wiki plugin stores articles as Markdown files in ./wiki/ with a
folder-driven URL structure. Admins and wizards manage pages with @wiki
in-game commands.

In-game wiki commands

@wiki/create <path>=<title>/<body>
  -- Create a new wiki page. Path mirrors the folder structure.
  -- Example: @wiki/create news/patch-notes=Patch Notes/Details here...

@wiki/edit <path>=<new body>
  -- Replace the body of an existing page (frontmatter is preserved).
  -- Example: @wiki/edit news/patch-notes=Updated content here.

@wiki/fetch <url>=<wiki-path>
  -- Download an image or PDF from a public URL into the wiki folder.
  -- Example: @wiki/fetch https://example.com/map.png=maps/world.png
  -- Allowed types: .jpg .jpeg .png .gif .webp .svg .pdf (max 10 MB)

Security note: @wiki/fetch blocks private/loopback/link-local IP
ranges (localhost, 127.x, 10.x, 192.168.x, etc.) to prevent SSRF attacks.
Only publicly routable URLs are permitted.

Wiki REST API

Method Endpoint Auth Description
GET /api/v1/wiki Required List all pages (title + path)
GET /api/v1/wiki?q=<query> Required Full-text search (title, body, tags)
GET /api/v1/wiki/<path> Required Read a page or list a directory
GET /api/v1/wiki/<path.ext> Required Serve a static asset (image, PDF)
POST /api/v1/wiki Staff Create a page
PATCH /api/v1/wiki/<path> Staff Update body and/or frontmatter
DELETE /api/v1/wiki/<path> Staff Delete a page or asset
PUT /api/v1/wiki/<path.ext> Staff Upload a static asset (binary)

Example — create a page:

curl -X POST https://yourgame.example.com/api/v1/wiki \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "news/patch-notes",
    "title": "Patch Notes",
    "date": "2026-03-18",
    "body": "## Changes\n- Added new location."
  }'

Wiki page frontmatter format:

---
title: My Page Title
date: 2026-03-18
author: Admin
tags: [news, update]
---

Page body in Markdown here.

All metadata keys must match /^[\w-]+$/. Body size limit is 10 MB.

Scene Management

Scenes are collaborative roleplay logs. They can be exported via the HTTP API.

Exporting a Scene

GET /api/v1/scenes/:id/export

Optional query parameter:

format Result
markdown (default) A formatted Markdown log with poses and participant list
json Full scene object as JSON

Example with curl:

# Markdown log
curl -H "Authorization: Bearer <token>" \
     https://yourgame.example.com/api/v1/scenes/42/export

# Raw JSON
curl -H "Authorization: Bearer <token>" \
     https://yourgame.example.com/api/v1/scenes/42/export?format=json

REST API Reference

All endpoints are served on the Hub’s HTTP port (default 4203). Most require
a Bearer JWT token in the Authorization header, obtained from
POST /api/v1/auth/login.

Authentication

Method Endpoint Description
POST /api/v1/auth/login Returns { token }
POST /api/v1/auth/register Create a new character
POST /api/v1/auth/reset-password Consume a reset token and set a new password
GET /api/v1/me Current user profile

Players

Method Endpoint Description
GET /api/v1/players/online List connected players
GET /api/v1/channels List channels

Bulletin Boards

Method Endpoint Description
GET /api/v1/boards All boards with post + unread counts
POST /api/v1/boards Create a board (staff)
GET /api/v1/boards/unread Unread summary across all boards
GET /api/v1/boards/:id Single board
PATCH /api/v1/boards/:id Update board (staff)
DELETE /api/v1/boards/:id Delete board + all posts (staff)
GET /api/v1/boards/:id/posts Paginated post list (limit, offset)
POST /api/v1/boards/:id/posts Create a post
GET /api/v1/boards/:id/posts/:num Read a post
PATCH /api/v1/boards/:id/posts/:num Edit a post (author or staff)
DELETE /api/v1/boards/:id/posts/:num Delete a post (author or staff)
POST /api/v1/boards/:id/read Mark board as read

Jobs

Method Endpoint Description
GET /api/v1/jobs List jobs (staff see all; players see their own)
POST /api/v1/jobs Submit a job
GET /api/v1/jobs/stats Counts by status, category, priority (staff)
GET /api/v1/jobs/:id Get a job by number or ID
PATCH /api/v1/jobs/:id Update status/priority/assignee (staff)
DELETE /api/v1/jobs/:id Delete a job (staff)
POST /api/v1/jobs/:id/comment Add a comment

Scenes

Method Endpoint Description
GET /api/v1/scenes List scenes
POST /api/v1/scenes Create a scene
GET /api/v1/scenes/locations List accessible rooms
GET /api/v1/scenes/:id Scene detail
PATCH /api/v1/scenes/:id Update name, desc, status, sceneType
POST /api/v1/scenes/:id/pose Add a pose/ooc/set entry
PATCH /api/v1/scenes/:id/pose/:poseId Edit a pose
POST /api/v1/scenes/:id/join Join a scene
POST /api/v1/scenes/:id/invite Invite a player
GET /api/v1/scenes/:id/export Export as ?format=markdown or ?format=json

Wiki

Method Endpoint Description
GET /api/v1/wiki List all pages
GET /api/v1/wiki?q=<query> Full-text search
GET /api/v1/wiki/<path> Read page or directory listing
GET /api/v1/wiki/<path.ext> Serve static asset
POST /api/v1/wiki Create page (staff)
PATCH /api/v1/wiki/<path> Update page (staff)
DELETE /api/v1/wiki/<path> Delete page or asset (staff)
PUT /api/v1/wiki/<path.ext> Upload static asset (staff)

WebSocket

Connect via ws://host:4202 (or the Hub port). Authenticate either by
sending connect <name> <password> as your first message, or by passing a
JWT at connection time:

ws://host:4203?token=<jwt>&client=web

The client=web parameter enables rich JSON payloads instead of plain text.

Troubleshooting

Common Issues

Server Won’t Start

  • Check if a port is already in use (4201, 4202, 4203)
  • Ensure Deno is installed and up to date (deno upgrade)
  • Check for errors in configuration output (deno task config)

Player Can’t Connect

  • Confirm the Hub is running (deno task server)
  • For Telnet clients, confirm the Telnet sidecar is running (deno task telnet)

Permission Denied Errors

  • Verify the player has the correct flags set (@set <player>=admin)
  • The wizard flag can only be granted by a superuser (@set <player>=wizard)

Logs

UrsaMU logs to stdout/stderr. Redirect output to a file if you need persistent
logs:

deno task server > logs/server.log 2>&1

Password Reset

See the full Password Reset guide for the step-by-step
flow. In brief:

  1. Run @resettoken <player> in-game — a UUID token is printed to your session
  2. Pass the token to the player out-of-band (Discord, email, etc.)
  3. The player calls POST /api/v1/auth/reset-password with { token, newPassword }
  4. The token is single-use and expires after 1 hour

Security

Securing Your Server

  • Place the Hub behind a reverse proxy (e.g., nginx, Caddy) for TLS termination
  • Set up a firewall to limit external access to only the necessary ports (4201, 4202, 4203)
  • Keep Deno and dependencies updated
  • Set a strong JWT_SECRET environment variable before starting (see below)
  • Use strong passwords — the auth.hash SDK method uses bcrypt

JWT Secret

UrsaMU signs session tokens with a secret key. Set it via environment variable
before starting the server:

export JWT_SECRET="your-long-random-secret-here"
deno task start

If JWT_SECRET is not set, a random secret is generated at startup and a
warning is printed. This means all sessions are invalidated on restart.

Brute-Force Login Protection

The login endpoint (POST /api/v1/auth/login) enforces a per-IP rate limit of
10 failed attempts per minute. After that threshold is reached the server
returns 429 Too Many Requests and logs the event to logs/security.log.

Security Headers

All HTTP responses include hardening headers:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: strict-origin-when-cross-origin

Security Logging

Security events are written to logs/security.log:

  • LOGIN_FAILED — wrong password
  • LOGIN_RATE_LIMITED — IP blocked after too many failures
  • PASSWORD_RESET — password reset token consumed
  • ADMIN_BOOT, ADMIN_TOAD, ADMIN_NEWPASSWORD — admin user management actions
  • ADMIN_SITE_SET, ADMIN_SITE_BLOCKED@site command activity
  • ADMIN_RESETTOKEN — reset token generation

The wizard Flag

The wizard flag sits at the same permission level as admin (level 9) but
can only be granted by a superuser. Regular admins cannot elevate another
player to wizard. Grant it with:

@set <player>=wizard