No description
  • TypeScript 52.1%
  • Svelte 38.2%
  • JavaScript 5%
  • HTML 1.7%
  • CSS 1.4%
  • Other 1.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jacob Davis 137e3396a6
All checks were successful
Tag Release / release (push) Successful in 8s
Release / prepare-release (push) Successful in 3s
Release / build-linux (push) Successful in 5m42s
Release / build-windows (push) Successful in 7m4s
Release / build-macos (push) Successful in 8m33s
fixed mics on mac
2026-10-25 10:51:54 -07:00
.forgejo fixed mics on mac 2026-10-25 10:51:54 -07:00
.vscode Initial commit 2026-02-17 13:10:09 -08:00
infra moved caddy outside of docker 2026-09-28 19:50:31 -07:00
landing added index.hml update, new deploy releases 2026-07-12 10:39:29 -07:00
src added better code in chats 2026-09-28 22:29:13 -07:00
src-tauri fixed mics on mac 2026-10-25 10:51:54 -07:00
static updated landing page 2026-02-18 16:05:49 -08:00
.gitignore removed code, and updated readme, added MIT license 2026-07-09 11:17:50 -07:00
.npmrc Initial commit 2026-02-17 13:10:09 -08:00
.prettierignore Initial commit 2026-02-17 13:10:09 -08:00
.prettierrc Initial commit 2026-02-17 13:10:09 -08:00
bun.lock added new code and markdown in chats 2026-09-28 12:35:48 -07:00
LICENSE removed code, and updated readme, added MIT license 2026-07-09 11:17:50 -07:00
package.json added new code and markdown in chats 2026-09-28 12:35:48 -07:00
README.md moved caddy outside of docker 2026-09-28 19:50:31 -07:00
svelte.config.js removed space 2026-06-24 00:11:41 -07:00
tsconfig.json Initial commit 2026-02-17 13:10:09 -08:00
vite.config.ts added fix for display nameing and matching 2026-08-09 22:53:57 -07:00
vitest.config.ts added tests for messages, and cleaned up code 2026-07-12 11:53:57 -07:00

RyftBorn

A self-hosted, open-source Discord alternative. Own your community.

  • Voids — Text channels
  • Drifts — Voice/video channels with screen sharing
  • Tethers — Direct messages
  • Self-hosted — Your data, your server, your rules

Built with SvelteKit, PocketBase, LiveKit, and Tauri.

RyftBorn Discord Equivalent
Ryft Server
Void Text Channel
Drift Voice Channel
Tether DM

Server Installation

Prerequisites

  • A Linux server (VPS, dedicated, or local machine)
  • Docker or Podman with Compose
  • A domain name pointed at your server (for production with TLS)
  • Ports 80, 443, 7881, 3478/udp, 50000-50200/udp open in your firewall

The examples below use podman. Replace with docker if that's what you have.

Podman setup (Linux only)

Podman needs a compose provider and Docker Hub registry configured:

pip3 install podman-compose
echo 'unqualified-search-registries = ["docker.io"]' >> /etc/containers/registries.conf

Quick Start (Local / Development)

This starts the full stack — PocketBase, the WebSocket server, LiveKit, the frontend (the web container, built from source, so the first start takes a few minutes), and a bundled Caddy for HTTPS.

git clone https://git.askdavis.com/AskDavis/RyftBorn.git
cd RyftBorn/infra

# Generate secrets and start containers
./setup.sh

setup.sh will:

  1. Prompt for a PocketBase admin email and password
  2. Generate a secret for LiveKit
  3. Write the .env file
  4. Start the containers
  5. Create the PocketBase superuser account
  6. Enable username login

Once complete, the services are available at:

Service URL
PocketBase http://localhost:8090
PB Admin UI http://localhost:8090/_/
WebSocket ws://localhost:7891
LiveKit ws://localhost:7880
Caddy https://localhost (landing) · https://app.localhost (app)

Run the frontend for development:

cd ..
bun install
bun run dev

Open http://localhost:5173 and register your first account. The first user automatically becomes the server owner.


Production Deployment

Production is the same stack pointed at a real domain: Caddy handles reverse proxying with automatic TLS, and LiveKit's embedded TURN server handles NAT traversal for voice/video. The frontend is built inside Docker — no Bun or Node needed on the server.

Caddy is deliberately not part of the app stack's normal deploy. The stack only publishes loopback ports (the frontend on 127.0.0.1:7892, the landing page on 7893, plus PocketBase, the WS server and LiveKit signaling); a Caddy in front of them owns ports 80/443. That way redeploying RyftBorn restarts only RyftBorn's containers — never the proxy that any other site on the server depends on. The routing lives in infra/caddy/ryftborn.caddy, used by either:

  • A Caddy installed on the host — use this if the server runs anything else (step 6a).
  • The bundled Caddy container — --profile edge, for a server that runs nothing but RyftBorn (step 6b).

1. Clone and configure

git clone https://git.askdavis.com/AskDavis/RyftBorn.git
cd RyftBorn/infra

2. Run setup

./setup.sh

3. Set your domain

Edit infra/.env and set your domain (both variables — DOMAIN is used by LiveKit's TURN server, DOMAIN_FRONTEND is what Caddy serves):

DOMAIN=ryftborn.example.com
DOMAIN_FRONTEND=ryftborn.example.com

Optionally add a Klipy API key for GIF support:

VITE_KLIPY_API_KEY=your_key_here

4. Enable TURN and external IP for LiveKit

Still in infra/.env, enable NAT traversal for production:

USE_EXTERNAL_IP=true
TURN_ENABLED=true
LIVEKIT_NODE_IP=

LiveKit's config is generated inside docker-compose.yml from these env vars — you don't need to edit any LiveKit config file. Leave LIVEKIT_NODE_IP empty in production; use_external_ip handles IP discovery.

5. DNS setup

Point two DNS records at your server:

Type Name Value
A ryftborn.example.com Your server IP
A app.ryftborn.example.com Your server IP

infra/caddy/ryftborn.caddy also serves mail.<domain> for a Stalwart mail server's web UI (expected on 127.0.0.1:8080). If you don't run one, delete that block.

6a. Caddy on the host (server shared with other sites)

Install Caddy from its official packages (apt install caddy on Debian/Ubuntu after adding the repo). Then:

# 1. Global options + an import for per-site files, in /etc/caddy/Caddyfile (replacing the
#    package's default welcome-page site):
#      {
#          servers {
#              protocols h1 h2      # no HTTP/3: PocketBase's SSE stream is unreliable over QUIC
#          }
#      }
#      import /etc/caddy/conf.d/*
sudo mkdir -p /etc/caddy/conf.d

# 2. RyftBorn's sites
sudo cp infra/caddy/ryftborn.caddy /etc/caddy/conf.d/

# 3. Tell Caddy the domain (and, optionally, who may reach the PocketBase admin UI)
sudo systemctl edit caddy
#      [Service]
#      Environment=RYFTBORN_DOMAIN=ryftborn.example.com
#      Environment=PB_ADMIN_ALLOW_CIDR=127.0.0.1/32

# 4. Start the app stack (no Caddy in it), then load the config
podman compose build
podman compose up -d
sudo systemctl restart caddy

systemctl reload caddy validates a config before applying it and keeps the old one if it's broken, so a bad edit never takes the other sites down. The error server block without any key is global configuration means RYFTBORN_DOMAIN isn't set in Caddy's environment.

6b. Bundled Caddy (server runs only RyftBorn)

podman compose build
podman compose --profile edge up -d

The edge profile adds a Caddy container on the host network that imports the same caddy/ryftborn.caddy, with DOMAIN_FRONTEND from .env as the domain. Keep passing --profile edge to later up/down commands, or that container is left out.

Either way, the stack runs:

Service What it does
PocketBase Auth, database, file storage
WS Server Real-time chat, typing indicators, presence
LiveKit Voice/video channels, screen sharing, embedded TURN for NAT
Web The built frontend + landing page (static files, loopback only)
Caddy Reverse proxy, automatic HTTPS certificates (host or bundled)

Caddy automatically provisions TLS certificates once DNS resolves.

7. First-time app setup

  1. Open https://app.ryftborn.example.com
  2. Register the first account — you become the server owner
  3. The Setup Wizard walks you through naming your server, setting visibility, and creating your first channel
  4. Share the link with others so they can register and join

Architecture

Browser / Tauri App
    │
    ├── HTTPS ──► Caddy (reverse proxy)
    │               ├── /api/*, /_/*       ──► PocketBase (auth, data, files)
    │               ├── /ws                ──► WS Server  (real-time chat)
    │               ├── /token             ──► WS Server  (LiveKit tokens)
    │               ├── /preview           ──► WS Server  (link previews)
    │               ├── /password-reset/*  ──► WS Server  (password reset)
    │               ├── /admin/*           ──► WS Server  (sign-up + privileged writes)
    │               ├── /registration/*    ──► WS Server  (join-request status)
    │               ├── /rtc*              ──► LiveKit    (voice/video signaling)
    │               └── /*                 ──► Web        (frontend SPA, static)
    │
    └── UDP/TCP ──► LiveKit (WebRTC media on 50000-50200/udp + 7881/tcp,
                    embedded TURN on 3478/udp for NAT traversal)

In development (without Caddy), the app connects to each service directly via port:

  • PocketBase: http://localhost:8090
  • WebSocket: ws://localhost:7891
  • LiveKit: ws://localhost:7880

In production, everything goes through Caddy on a single domain with automatic TLS. Caddy runs outside the app stack (on the host, or the bundled container on the host network) and reaches every service on a loopback port.


Firewall Ports

Port Protocol Service When needed
80 TCP Caddy (HTTP redirect) Production
443 TCP Caddy (HTTPS) Production
3478 UDP LiveKit embedded TURN Production
7881 TCP LiveKit RTC (TCP fallback) Production
50000-50200 UDP LiveKit RTC media Production
8090 TCP PocketBase Dev only
7891 TCP WebSocket server Dev only
7880 TCP LiveKit signaling Dev only
7892, 7893 TCP Web (app, landing page) Loopback only — Caddy proxies to them

In production, only ports 80, 443, 7881, 3478/udp, and 50000-50200/udp need to be open. Caddy handles all HTTP(S) routing; WebRTC media flows directly to LiveKit.


Common Operations

# View logs
podman compose logs -f                # all services
podman compose logs -f pocketbase     # single service

# Restart (add --profile edge to both if you use the bundled Caddy)
podman compose down && podman compose up -d

# Rebuild after code changes (the frontend lives in the web image, the real-time server in ws-server)
podman compose build web ws-server
podman compose up -d web ws-server

# After changing infra/caddy/ryftborn.caddy (host Caddy): copy it over and reload — reload keeps
# the running config if the new one doesn't validate. The bundled Caddy: restart its container.
sudo cp infra/caddy/ryftborn.caddy /etc/caddy/conf.d/ && sudo systemctl reload caddy

# PocketBase admin UI — NOT exposed publicly. Caddy answers 404 for /_/ unless your address is in
# PB_ADMIN_ALLOW_CIDR (see .env.example). Tunnel to the host's loopback bind instead:
#   ssh -L 8090:127.0.0.1:8090 <server>   then open   http://localhost:8090/_/
#   Dev: http://localhost:8090/_/ directly

# Back up data — PocketBase snapshots the database AND uploaded files into a zip every night at
# 03:00 (infra/pocketbase/pb_data/backups/, last 7 kept). Copy those zips somewhere off the box.
# Take one now, or restore one, from the admin UI → Settings → Backups.
# Do NOT `cp -r pb_data` of a running server: SQLite's write-ahead log makes a plain copy inconsistent.
ls infra/pocketbase/pb_data/backups/

# Reset everything (destroys all data)
podman compose down
rm -rf infra/pocketbase/pb_data
./setup.sh

Upgrading

cd RyftBorn && git pull
cd infra
podman compose build web ws-server     # frontend + real-time server are built from source
podman compose up -d                   # PocketBase applies any new pb_migrations on start
# If the release touched infra/caddy/ryftborn.caddy and you run Caddy on the host:
sudo cp caddy/ryftborn.caddy /etc/caddy/conf.d/ && sudo systemctl reload caddy

Check podman compose logs pocketbase after the restart. A failed migration stops PocketBase; restore the newest zip from pb_data/backups/ (admin UI → Settings → Backups) and report the failing migration.

PocketBase and LiveKit are pinned with POCKETBASE_VERSION / LIVEKIT_VERSION in .env (see .env.example). Bump those deliberately, one at a time, with a fresh backup on hand — a latest tag can jump a major version underneath your database.


Desktop App (Tauri)

The desktop app wraps the same SvelteKit frontend in a native window.

bun install
bun run tauri dev

When connecting from the desktop app, enter your server address (e.g. https://app.ryftborn.example.com) in the connection dialog. The app automatically derives WebSocket and LiveKit URLs from it.


Releases

Pushing to main redeploys the server and cuts a desktop release. No version number is ever committed:

  • src-tauri/tauri.conf.json and src-tauri/Cargo.toml stay at 0.0.0 in the repo. The git tag is the source of truth for a version.
  • CI reads the newest v* tag, pushes the next patch tag, and the release workflow stamps that number into its own checkout at build time (.forgejo/stamp-version.mjs). Nothing is committed back to main, so your clone never falls behind after a push and git pull stops producing merge commits.
  • The build aborts if the stamped version does not appear in the installer's filename. An app whose version disagrees with the published update manifest makes every client reinstall the same build forever, so that mismatch must never ship.

Put [skip ci] in a commit message to redeploy the server without cutting a desktop release.

To rebuild a version that already exists, re-run the Release workflow against its tag.


Tech Stack

  • Frontend: SvelteKit + Svelte 5 + TypeScript + Tailwind v4
  • Desktop/Mobile: Tauri v2
  • Backend: PocketBase (auth, CRUD, files, embedded SQLite)
  • Real-time: Bun WebSocket server
  • Voice/Video: LiveKit Server
  • Reverse Proxy: Caddy (automatic TLS)
  • NAT Traversal: LiveKit embedded TURN
  • Icons: Lucide Svelte
  • Package Manager: Bun

Project Structure

src/              # SvelteKit frontend (Svelte 5 + TypeScript + Tailwind v4)
src-tauri/        # Tauri v2 native shell
infra/            # Server infrastructure
  docker-compose.yml    # All services (LiveKit config generated inline from env vars)
  Dockerfile.web        # Multi-stage build: frontend → static file server
  web/Caddyfile         # Static serving inside the web container (plain HTTP)
  caddy/ryftborn.caddy  # Edge proxy sites: TLS + routing (host Caddy or bundled)
  Caddyfile             # Bundled edge Caddy (--profile edge) — imports caddy/ryftborn.caddy
  ws-server/            # Bun WebSocket server
  pocketbase/
    pb_migrations/      # Database schema
    pb_data/            # Database + file storage (created at runtime)
  setup.sh              # First-time setup script
  .env                  # Secrets (generated by setup.sh, not committed)
landing/          # Static landing page served at root domain

License

MIT — see LICENSE.