Storage and media
Uploaded files lie encrypted at rest in the data volume, and the node converts images and videos with its own media pipeline.
Encryption at rest and the storage key
Section titled “Encryption at rest and the storage key”Stored files are encrypted at rest (TAVRN_STORAGE_ENCRYPTION=on, the default): attachments, avatars, headers,
room icons, custom emojis and soundboard clips lie in data/blobs as AES-256-GCM ciphertext, each file with its
own key, and those keys are wrapped with the node’s storage key keys/storage.key (32 random bytes, mode
0600, created at the first start) and kept in the database (blob_keys). The node decrypts while streaming a
download, so the blob directory can be copied, synced or later moved to object storage without exposing content.
This protects the storage layer, it is not end-to-end encryption (the node holds the keys; attachments of
encrypted conversations are ciphertext anyway and are simply wrapped once more).
Back up keys/storage.key together with the database and the volume: without it every stored file is
unreadable. The node refuses to start when the file is missing or does not match the stored keys, instead of
creating a new one. Files written by older versions are encrypted in place in the background after the start
(resumable, about 32 MB/s, progress in the log). TAVRN_STORAGE_ENCRYPTION=off stores new files unencrypted (for
debugging only; encrypted files stay readable, a warning is logged at every start).
Rotating the storage key
Section titled “Rotating the storage key”The storage key that wraps every file’s key can be replaced in seconds, because the files themselves are not re-encrypted.
Stop the node, run the rotation, start it again and back up the new key file:
docker compose stop tavrndocker compose run --rm tavrn tavrn-node rotate-storage-keydocker compose start tavrnThe rotation creates keys/storage.key.next, re-wraps every file key with it (the files themselves are not
re-encrypted, so it takes seconds) and then replaces keys/storage.key. An interrupted run is finished by running
it again; a node started in between reads files wrapped with either key. Because the files are not re-encrypted,
an old key together with an old database backup still opens the files of that time. The command prints the old and
new key ids and the number of re-wrapped file keys, and warns about files wrapped with a key the node does not have
(those are unreadable). With several instances stop all of them first.
Media pipeline (ffmpeg)
Section titled “Media pipeline (ffmpeg)”Avatars, profile headers, room icons and video backgrounds are converted by ffmpeg. The image contains a
minimal static ffmpeg (about 7 MB, only image and short-video decoders and the WebP encoder, no network
protocols). Uploads are limited to 20 MB and 30 seconds and converted without network access with a 30-second
limit per job, TAVRN_MEDIA_WORKERS (default 1) at a time; animated input becomes animated WebP with a still first
frame. Results are at most 1 MB per variant for avatars and icons and 2 MB for headers. TAVRN_MEDIA_FFMPEG=off
turns the conversion off (static PNG/JPEG/WebP only); outside the image point TAVRN_MEDIA_FFMPEG at an ffmpeg
with libwebp (just ffmpeg-install).
Uploads, quotas and retention
Section titled “Uploads, quotas and retention”Upload size, types, the executable rule, per-room quotas and attachment retention are listed under
Rooms, uploads and storage and Entitlement defaults. The
hourly housekeeping deletes expired attachments (messages keep listing them as “file expired”; pinned messages keep
theirs), orphaned files and cached copies of other nodes’ emojis and clips (TAVRN_MEDIA_CACHE_UNUSED_DAYS).
Files channels on the node’s storage have their own quota (TAVRN_FILES_CHANNELS_MAX_BYTES per channel; channels on
an external store are never limited by it, see below); they do not count towards the
room’s attachment quota and are exempt from attachment and message retention. They can use external WebDAV/S3
stores only with TAVRN_FILES_EXTERNAL=on (Files channels on external stores).
Who may upload what
Section titled “Who may upload what”Every file falls into one upload category: images, audio, video, documents, archives or other
(protocol: PROTOCOL.md §7a, permissions.md “Upload categories”). The node classifies plaintext uploads by their
content first, then by the file name, then by the declared type; in encrypted conversations and encrypted files
channels the node only sees ciphertext, so the client declares the category and the node checks the declaration
(a modified client could lie; the protocol documents this).
- Rooms decide per role which categories its members may upload (the role’s “Allowed file types”;
@everyoneallows all six by default, the highest role with its own setting wins), and channel or category overrides allow or deny single categories on top (“File types” in the permissions of a channel or category), evaluated like permissions. Members see what they may attach before they pick a file; a refused upload gets403 upload.type_forbiddenwith the category. - DMs and group DMs hosted on this node use
TAVRN_UPLOAD_DM_CATEGORIES(default all six). - Executables (programs, scripts, installers, disk images, shortcuts, macro documents) count as
otherand are refused on every node by default. OnlyTAVRN_UPLOAD_ALLOW_EXECUTABLES=trueand a member who may uploadotherlets them through; no room setting can allow them on its own. Where they are allowed, the clients still warn before they download a file that looks like a program. TAVRN_UPLOAD_ALLOWED_TYPESadditionally limits the declared MIME types of attachments in unencrypted conversations; files channels and encrypted uploads are not affected by it.
Files channels: what the node stores
Section titled “Files channels: what the node stores”New files channels are end-to-end encrypted by default (protocol: FILES.md §8); the room’s managers choose this when they create a channel and cannot change it later. In an encrypted files channel the node (and an external WebDAV/S3 store) only ever holds what the clients encrypted: the file contents and client-made thumbnails as ciphertext, and per entry an encrypted “entry record” with the name, type and key. In plaintext it keeps the entry ids, the folder tree (which entry is in which folder), who uploaded what and when, and the ciphertext sizes; never a name, a folder name, a type or content. Quotas, the entry limit, the orphan sweep, deletes and the audit log work on those fields (the audit log names entry ids instead of file names). Thumbnails of encrypted channels are always kept in the node’s own storage, also for channels on an external store. The node cannot search, preview or classify encrypted files; upload categories are declared by the clients. Members who lost their keys can still list and delete their own uploads.
Unencrypted files channels work as before: their files use the storage encryption at rest (readable by the node)
or, on an external store, encrypt_files (which encrypted channels do not need and refuse).
Files channels on external stores
Section titled “Files channels on external stores”With TAVRN_FILES_EXTERNAL=on (default off, protocol: FILES.md §5) members with Manage channels and Manage
files can keep a files channel on a WebDAV server or an S3-compatible bucket instead of the node’s own storage. The
node proxies uploads and downloads as streams and keeps the folder tree in its database; it never lists the store.
- Credentials (URL, user name and password or access keys) are stored sealed with
keys/secrets.key, are write-only in the API and never appear in logs or the audit log (files.backendrecords kind, host and encryption). Withoutsecrets.keythe node cannot reach the stores any more: back it up with the other keys. encrypt_fileswraps new files with the storage key before they reach the store (unreadable outside tavrn and lost withoutkeys/storage.key); without it the store keeps the files as they are. End-to-end encrypted files channels store the clients’ ciphertext either way and refuse the switch.- Network rules:
httpsonly, every address the store’s name resolves to must be public, redirects are never followed, 10 s connect and 60 s read timeouts.TAVRN_FILES_EXTERNAL_PRIVATE=onalso allows private, loopback and link-local addresses and plainhttp(stores in your LAN); this turns the SSRF protection off for them. - Switching it off: with
TAVRN_FILES_EXTERNAL=offexisting external channels answer403 files.external_disableduntil it is on again; nothing is deleted, and the queue of pending deletes on the stores (below) pauses. - A channel can move to another store only while it is empty; credentials and
encrypt_filescan change at any time. Backend changes and connection tests are limited to 20 per user and 10 minutes. - Limits:
TAVRN_FILES_CHANNELS_MAX_BYTES(and plans and grants offiles_channels.max_bytes) covers only the node’s own storage; it never limits a channel on an external store, since the node does not keep those bytes. Such a channel has its own optional limit, “Limit on the store” in its storage settings (max_bytes, empty or 0 = unlimited), set by whoever may configure the store; the node enforces it on the uploads it relays (413 files.quota_exceeded) and the clients show it as the channel’s limit.
External stores only. TAVRN_FILES_INTERNAL=off (default on; needs TAVRN_FILES_EXTERNAL=on, otherwise
the node does not start and check-config names the problem) keeps the files of files channels off the node: large
shared folders live on the members’ own WebDAV or S3 stores, attachments in chats stay on the node as before.
-
A new files channel is created without a store. Until its managers choose a WebDAV or S3 store in the channel settings (“Storage”), uploads and new folders answer
403 files.internal_disabled; the clients show “choose a store first” instead of the upload button. The node’s own storage cannot be chosen (403 files.internal_disabled). -
Clients see the policy before the first upload:
files_internal: falseinGET /api/v1/nodeandpolicies.files_internalin the discovery document. -
The quota:
TAVRN_FILES_CHANNELS_MAX_BYTESdoes not limit the external stores (see “Limits” above), so it can stay as it is for the channels that still keep files on the node. -
Exceptions per owner: the entitlement
files_channels.internal(a room key, node defaultTAVRN_FILES_INTERNAL) decides per room. A plan or per-user override of the room’s owner with"files_channels.internal": 1(or a grant of the room itself) allows the node’s own storage in that owner’s rooms while the node default is off, e.g. for a paid plan; the quotafiles_channels.max_bytesapplies there as usual. The other direction works too:0sends an owner’s rooms to external stores on a node that keeps files. A grant that does not name the key leaves the node default; among several active grants that name it,1wins. A member’s own plan does not count in somebody else’s room. Clients read the room’s value asinternal_allowedin the channel’s files usage and offer “This node” in the storage settings only where it istrue;GET /api/v1/nodekeepsfiles_internalas the node default. When the grant ends, the room behaves like any channel of a node switched off (below): the stored files stay, new uploads are refused.Terminal window curl -sX POST https://chat.example/api/v1/admin/entitlements/grants -H "Authorization: Bearer $TOKEN" \-H 'Content-Type: application/json' \-d '{"subject":"user:alice","overrides":{"files_channels.internal":1},"note":"node storage for files channels"}' -
Switching an existing node: channels that already keep files on the node keep them (listing, downloads, renames, moves and deletes work), but take no new uploads; once such a channel is empty its managers can move it to an external store. Thumbnails of encrypted channels (at most 1 MiB each) stay on the node, as for every external channel.
Deleting a files channel or a room deletes the files in the node’s storage at once. Objects on an external store are
deleted in the background with the credentials stored for the channel; when the store is unreachable they wait in a
queue (file_pending_deletes, the configuration sealed with secrets.key) that the hourly housekeeping retries for
7 days before it gives up and logs the object key. While TAVRN_FILES_EXTERNAL=off the queue pauses. On request
(keep_external, offered by the client only for unencrypted external channels) the objects stay on the store;
the audit log records how many were kept.
Message retention
Section titled “Message retention”TAVRN_RETENTION_DAYS (default 0 = keep forever, at most 36500) deletes messages this many days after they were
sent: the hourly housekeeping removes them from channels, DMs and group DMs hosted on the node, encrypted or not,
together with their attachments (rows and files), reactions, pins and search entries; threads count their messages
again. Retention never deletes pinned messages (unpin a message to let it expire)
and the entries of a room’s welcome channel (the room’s security log), together with the security notices in
channels (see Moderation and auto-moderation). A room owner or a member with “Manage
room” can set a shorter retention for one room in the room settings (message_retention_days, 1–36500, 0 = the
node’s value); a longer room value has no effect, a room can only shorten. Each run that deleted something writes one
entry retention.messages to the node’s audit log (deleted, node_days, rooms with an own shorter value,
incomplete when the run’s time budget of one minute ran out; the rest follows an hour later). Clients are not told:
an open client keeps showing what it already loaded until it reloads, and messages users saved themselves (exports,
the local search index of encrypted conversations) are outside the node’s reach. tavrn-node check-config refuses
values above 36500. Tell your users in the privacy policy.
Profiles, presence and message requests
Section titled “Profiles, presence and message requests”What users see, so you can answer their questions: users edit their profile under Settings → Profile: display name, avatar and profile header (PNG, JPEG, GIF, WebP or a short MP4/WebM; the node crops and converts them, animated ones loop for up to ten seconds and only move while someone hovers them or while the user speaks in voice), a short Markdown bio and up to ten linked accounts (Steam, Battle.net, Xbox, PlayStation, Nintendo, Twitch, YouTube, X, Mastodon, a website; shown as “not verified”). A click on a name or avatar opens the profile card. The footer card sets the status (online, idle, do not disturb, invisible), a custom status and an activity; the client reports idle after ten minutes without input. Settings → Privacy & safety sets who can message you (friends, friends and shared rooms, everyone), hides your activity and lists the people you blocked with your private notes. Messages from people who are not your friends wait under Message requests until you accept them; friend requests can carry a short message. The ”+” in the room list also joins rooms from an invite link or code.
For operators: TAVRN_DM_POLICY is the upper bound of what users may allow (friends, everyone_shared,
everyone; default everyone). Presence is kept per node: clients push it to every node they use, nodes never
forward it to each other.
Profiles of users of other nodes are copies (mirrors) kept by this node, images included, so clients never load
anything from other nodes. When a user changes the public profile (name, avatar, header, bio, linked accounts, DM
setting; never the private voice background), the home node tells the nodes that mirror it (profile.changed,
FEDERATION.md §5.2): the nodes the user signed in on and the home nodes of friends and DM contacts. The message
leaves about 5 to 15 seconds after the first change, one per user and node however many edits follow, and the
other node refreshes its copy in the background (at most 10 refreshes per user and 10 minutes; beyond that it
answers 429 and the outbox retries). Nodes of older versions ignore the message and refresh on the user’s next
federated login there or after 24 hours. Nothing needs to be configured.
Whiteboard
Section titled “Whiteboard”Every conversation has a whiteboard: the 🖍 button in the header of a channel, DM or group DM opens it full screen
(Excalidraw: text, shapes, freehand, images). The one who opens it presents it; the others see a live dot on the
button and join on demand (“Join whiteboard session”), watch live with named cursors and can leave again. Only the
presenter edits until they open the board for everyone, grant single participants (“Who can edit”) or lock it again.
The presenter sets a background color or image, keeps a personal library of scenes (save, load, rename, delete; the
current scene is saved every 30 seconds), and closes the board for everyone. Anyone in the session can save the board
as PNG or SVG or share it as an image into a channel or DM. Phones pan and zoom the board with the usual gestures; the
board’s actions sit in the ”⋯” menu there. In a voice call the 🖍 control presents a board on your tile (instead of a
screen share; one at a time): the others see a badge and a live preview on your tile (and a badge in the channel
list), join from the tile (or by double-clicking it) and get it as a large tile they can focus, maximize or pop out
into its own window. Changes travel end-to-end encrypted like messages and are never stored on the node; scenes are
stored encrypted on the user’s home node
(WHITEBOARD.md). Operators limit the library
with TAVRN_WHITEBOARD_MAX_SCENES (default 50 scenes of at most 5 MB per user).
This page is generated from tavrn-node/docs/HANDBOOK.md (AGPL-3.0-only) when the website is built; its links were adapted for this site.