Storage and backups
The server keeps all its data as plain files in the database directory you pass to use. There’s no separate database server. This page lists what’s in the directory and how to back it up safely.
What’s in the database directory
Section titled “What’s in the database directory”| Path | Contents |
|---|---|
config.json |
Your settings. Only the values you changed. |
.env |
Secrets such as ROTUR_TOKEN. |
users.json |
Every account, keyed by internal user ID. Local account passwords are stored as bcrypt hashes. |
roles.json |
Roles, keyed by name. |
channels.json |
The ordered channel list, including separators. |
channels/<channel>/ |
Messages, in numbered JSONL files (0000.jsonl, 0001.jsonl, …) of up to 1,000 messages each. |
threads/, threads_index.json |
Thread details and thread messages. |
users/<id>/ |
Each account’s unread state and mention history. |
polls/ |
One file per poll. |
attachments/ |
Attachment details (<id>.json) and, with local storage, the files themselves. |
attachment_ids.txt, attachment_stats.json |
Attachment index and totals. |
avatars/ |
Local account avatars. |
serverAssets/ |
Server icon and banner uploads. |
serverEmojis/, serverEmojis.json |
Custom emoji images and their details. |
serverStickers/, serverStickers.json |
Sticker images and their details. |
plugins/<name>/ |
Installed plugins: plugin.json, main.js, data.json (the plugin’s stored data), and an optional files/ directory. |
access.json |
Access mode, whitelist, server password hash, applications, and invites. |
reports.json |
Reports. |
webhooks.json |
Webhooks and their tokens. |
modlog.jsonl |
The audit log, one entry per line. |
rate_limits.json |
Active timeouts, so they survive a restart. |
welcome.json |
Accounts that have already been welcomed. |
mentions-version.json |
Marks the mention history migration as done. |
backup/ |
Created empty by init. The server never writes to it. |
You may also see these working files:
| Path | Meaning |
|---|---|
*.pending next to a message file |
Recent edits not yet merged into the message file. Part of your data. Back it up with its message file. |
.message-index/ inside a channel or thread directory |
A search index for fast paging. The server can rebuild it. See Rebuilding message indexes. |
write-transaction.json and *.undo |
Present only while a multi-file write is in progress. See Interrupted writes. |
*.migrated |
Old files kept after a migration. |
Attachment files live outside this directory if you set attachments.local_dir or use S3 storage.
What to back up
Section titled “What to back up”A complete backup has:
- The whole database directory, including every
.pendingfile. - The
attachments.local_dirdirectory, if you set one. - The S3 bucket, or a versioned snapshot of it, if you use S3.
Back up the whole directory, not individual files. The files refer to each other. A copy of the messages without the thread index, polls, or attachment details is a broken server.
.message-index/ directories are optional in a backup. Leave them out and the server rebuilds them on first use.
.env contains live secrets. Store it separately from backups you share or keep off-site.
Make a backup
Section titled “Make a backup”The simplest consistent backup is taken while the server is stopped:
# Stop the server first (Ctrl+C, or stop your service).tar -czf ~/backups/originchats-$(date +%Y%m%d-%H%M).tar.gz \ --exclude='.message-index' \ -C ~/originchats-osl db# Start the server again.Keep backups outside the database directory, so each backup doesn’t include the previous ones.
A copy taken while the server runs can catch a file halfway through a change, or one file changed and a related file not yet changed. If you can’t stop the server, use a filesystem snapshot (LVM, ZFS, or Btrfs) so every file is captured at the same instant.
Restore a backup
Section titled “Restore a backup”Restore into a new directory first, not over the live one:
mkdir ~/restore-testtar -xzf ~/backups/originchats-20260901-0300.tar.gz -C ~/restore-test./originchats-osl use ~/restore-test/dbThen check startup logs, channels, old and new messages, threads, polls, unreads, attachments, roles, webhooks, reports, and the audit log. A backup isn’t proven until a restore like this works.
How writes stay safe
Section titled “How writes stay safe”- Most files are rewritten by writing a temporary file and renaming it over the original. A crash leaves either the old or the new version, never half of one. This needs the temporary file and the original on the same filesystem, which they are unless you move files around by hand.
- A new message is saved before anyone is told about it.
- Changes that touch several files at once use a transaction journal. See the next section.
- Failed saves are rolled back in memory as well, so the running server never shows something that isn’t on disk.
Interrupted writes
Section titled “Interrupted writes”For a change that touches several files, the server first writes write-transaction.json. This journal lists each file’s original size, and a .undo copy of each file it will replace. The server deletes the journal once every file is written.
If the process stops in the middle, the journal is still there at the next start. Before loading anything, the server uses it to put every file back the way it was, then deletes the journal and the .undo copies.
If that recovery fails, the server logs unable to recover interrupted storage transaction and exits. Check disk space and file permissions in the database directory, then start again. Don’t delete write-transaction.json to get past the error: that would leave a change half applied. If recovery keeps failing, restore from a backup.
Rebuilding message indexes
Section titled “Rebuilding message indexes”Each channel and thread directory can hold a .message-index/ directory. It lets the server jump to any page of history without reading every earlier message. It is built the first time history is read after startup, which can take a while on a large channel, and kept up to date after that.
The message files are the source of truth, and the index can always be rebuilt. Rebuild it after you edit, import, or replace message files by hand, or if you suspect it’s damaged:
- Stop the server.
- Delete the channel’s or thread’s
.message-index/directory. - Start the server. The index is rebuilt the next time that history is read.
See persistent message indexes for how the index works.
Connection details aren’t stored
Section titled “Connection details aren’t stored”Which client and device each member is using is kept in memory only, for as long as they’re connected. It isn’t written to users.json or to messages, and a restart clears it until members log in again.
The bot flag on an account is stored in users.json. It defaults to false, and it doesn’t grant any permissions.
Legacy data
Section titled “Legacy data”The server can read, and in some cases migrate, older storage layouts, including data from the legacy Python server. See legacy formats before moving a database from another server, and keep the original backup.
