docs: tighten public copy, add comparison, templates, and social preview
* docs: tighten README, CONTRIBUTING, and CLA prose * docs: add platform comparison and README FAQ * chore: add issue and pull request templates * chore: add repository social preview image
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
# Backspace
|
||||
|
||||
**A self-hosted communication platform — text, voice, video, and federation — that you own.**
|
||||
**A self-hosted communication platform you own. Text, voice, video, and federation.**
|
||||
|
||||
[](LICENSE)
|
||||
[](https://www.typescriptlang.org/)
|
||||
@@ -15,41 +15,41 @@
|
||||
|
||||
---
|
||||
|
||||
Backspace is a Discord-style chat platform you run on your own hardware. Spaces,
|
||||
channels, roles, voice and video, screen sharing, direct messages, friends, file
|
||||
sharing, and message search — plus **server-to-server federation**, so
|
||||
independent Backspace instances can talk to each other while each stays under
|
||||
its own control.
|
||||
Backspace is a self-hosted, open-source Discord alternative: a Discord-style chat
|
||||
platform you run on your own hardware. Spaces, channels, roles, voice and video,
|
||||
screen sharing, direct messages, friends, file sharing, and message search. On top
|
||||
of that, **server-to-server federation** lets independent Backspace instances talk
|
||||
to each other while each stays under its own control.
|
||||
|
||||
It is **free and open source** under the **GNU AGPL-3.0**, and dual-licensed: a
|
||||
commercial license is available if the AGPL doesn't fit your use. See
|
||||
[License](#license) for the details.
|
||||
|
||||
> **Project status** <a name="project-status"></a>
|
||||
> Backspace 1.0 — stable, self-hostable, and actively developed.
|
||||
> Backspace 1.0. Stable, self-hostable, and actively developed.
|
||||
|
||||
## What makes Backspace different
|
||||
|
||||
Self-hosted chat usually forces a trade-off: gaming-grade voice and video, *or* a
|
||||
polished Discord-style experience, *or* federation between independent servers —
|
||||
rarely all three, and rarely with the fine-grained media controls people expect.
|
||||
polished Discord-style experience, *or* federation between independent servers.
|
||||
Rarely all three, and rarely with the fine-grained media controls people expect.
|
||||
|
||||
Backspace does all three at once:
|
||||
|
||||
- **Voice & video with a real control surface.** Not just "it has screen share":
|
||||
choose resolution, frame rate, codec (VP9 or hardware H.264), and bitrate; set
|
||||
independent 0–200% volume for every person *and* every screen-share; RNNoise
|
||||
noise suppression; a live connection inspector (bitrate, codec, ping, packet
|
||||
loss, jitter); and a per-tile badge showing each stream's measured
|
||||
resolution/frame-rate. Screen sharing goes up to 4K/120fps within admin-set
|
||||
- **Voice and video with a real control surface.** This goes past a screen-share
|
||||
button. Choose resolution, frame rate, codec (VP9 or hardware H.264), and
|
||||
bitrate; set independent 0-200% volume for every person and every screen-share;
|
||||
RNNoise noise suppression; a live connection inspector (bitrate, codec, ping,
|
||||
packet loss, jitter); and a per-tile badge showing each stream's measured
|
||||
resolution and frame-rate. Screen sharing goes up to 4K/120fps within admin-set
|
||||
bounds.
|
||||
- **Federation, not a walled garden.** Run your own instance and peer it with
|
||||
others: cross-instance friends, DMs, calls, and presence — each server
|
||||
independently owned, requests HMAC-authenticated.
|
||||
- **A complete, polished platform — not a demo.** Role-based permissions with
|
||||
per-category and per-channel overrides, friends and group DMs, inline playable
|
||||
media, moderation with audit trails, search, a desktop app, and an installable
|
||||
mobile PWA — all in the warm, calm "Aether Drift" interface.
|
||||
others for cross-instance friends, DMs, calls, and presence. Each server stays
|
||||
independently owned, and requests are HMAC-authenticated.
|
||||
- **A complete platform, not a demo.** Role-based permissions with per-category
|
||||
and per-channel overrides, friends and group DMs, inline playable media,
|
||||
moderation with audit trails, search, a desktop app, and an installable mobile
|
||||
PWA, all in the warm, calm "Aether Drift" interface.
|
||||
|
||||
You own the server, the data, and the network it federates into.
|
||||
|
||||
@@ -59,7 +59,7 @@ You own the server, the data, and the network it federates into.
|
||||
|
||||
<img src="docs/screenshots/voice-video-grid.webp" alt="A voice channel with a grid of camera and screen-share tiles" width="900" />
|
||||
|
||||
<sub><em>A voice channel in full swing — camera tiles alongside live screen-shares, each with its own resolution / frame-rate label.</em></sub>
|
||||
<sub><em>A voice channel in full swing. Camera tiles alongside live screen-shares, each with its own resolution and frame-rate label.</em></sub>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -67,31 +67,31 @@ You own the server, the data, and the network it federates into.
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/chat.webp" alt="A text channel with messages and a typing indicator" /><br/>
|
||||
<sub><b>Text channels</b> — Markdown, replies, reactions, and live typing indicators.</sub>
|
||||
<sub><b>Text channels.</b> Markdown, replies, reactions, and live typing indicators.</sub>
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/screen-share-settings.webp" alt="The screen-share settings popover" /><br/>
|
||||
<sub><b>Screen-share controls</b> — resolution, frame rate, codec, and bitrate, within admin-set bounds.</sub>
|
||||
<sub><b>Screen-share controls.</b> Resolution, frame rate, codec, and bitrate, within admin-set bounds.</sub>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/space-discovery.webp" alt="The space discovery / Explore view" /><br/>
|
||||
<sub><b>Spaces & discovery</b> — browse public, request-to-join, and joined spaces.</sub>
|
||||
<sub><b>Spaces and discovery.</b> Browse public, request-to-join, and joined spaces.</sub>
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/group-dm.webp" alt="A federated group direct message" /><br/>
|
||||
<sub><b>Direct messages</b> — 1-on-1 and group DMs, including members on peer instances.</sub>
|
||||
<sub><b>Direct messages.</b> 1-on-1 and group DMs, including members on peer instances.</sub>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/user-discovery.webp" alt="The find-people / user discovery view" /><br/>
|
||||
<sub><b>Friends & social</b> — find people across instances with mutual friends and spaces.</sub>
|
||||
<sub><b>Friends and social.</b> Find people across instances with mutual friends and spaces.</sub>
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/admin-federation.webp" alt="The federation admin panel showing peered instances" /><br/>
|
||||
<sub><b>Federation admin</b> — manage peered instances, relay, and secret rotation.</sub>
|
||||
<sub><b>Federation admin.</b> Manage peered instances, relay, and secret rotation.</sub>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -106,22 +106,22 @@ You own the server, the data, and the network it federates into.
|
||||
- Message reactions, replies, editing, deletion, and per-message mark-as-unread
|
||||
- Rich link embeds (YouTube, Vimeo, Spotify, and generic OpenGraph) with SSRF-protected scraping, plus GIF search (Klipy)
|
||||
- Typing indicators, unread badges, and presence
|
||||
- Direct messages — 1-on-1 and group DMs (up to 10 people), with voice/video calls (ring / accept / reject)
|
||||
- Direct messages: 1-on-1 and group DMs (up to 10 people), with voice/video calls (ring, accept, reject)
|
||||
|
||||
**Voice, video & screen sharing** (via [LiveKit](https://livekit.io/)):
|
||||
- Screen sharing up to 4K / 120fps — VP9 by default, an optional hardware-accelerated H.264 mode, and a VP8 simulcast fallback
|
||||
- Per-stream quality controls — resolution, frame rate, codec, and bitrate, within admin-set bounds
|
||||
- Independent 0–200% volume for every participant *and* every screen-share
|
||||
**Voice, video, and screen sharing** (via [LiveKit](https://livekit.io/)):
|
||||
- Screen sharing up to 4K/120fps: VP9 by default, an optional hardware-accelerated H.264 mode, and a VP8 simulcast fallback
|
||||
- Per-stream quality controls: resolution, frame rate, codec, and bitrate, within admin-set bounds
|
||||
- Independent 0-200% volume for every participant and every screen-share
|
||||
- RNNoise noise suppression (on by default), plus echo-cancellation and auto-gain toggles and mic/speaker device selection
|
||||
- Live connection inspector — per-participant bitrate, codec, ping, packet loss, and jitter — plus a per-tile badge showing each stream's measured resolution/frame-rate
|
||||
- Live connection inspector for per-participant bitrate, codec, ping, packet loss, and jitter, plus a per-tile badge showing each stream's measured resolution and frame-rate
|
||||
- Screen-share viewer detection ("who's watching") and auto-ducking that lowers stream audio when someone speaks
|
||||
- Selective subscription — mute or stop watching any camera/stream to save bandwidth
|
||||
- Selective subscription: mute or stop watching any camera or stream to save bandwidth
|
||||
- Push-to-talk and fully customizable keybinds (including mouse buttons), in the browser and the desktop app
|
||||
- Picture-in-Picture for voice and video
|
||||
|
||||
### Organization
|
||||
- Spaces with channel categories
|
||||
- Role-based permissions — bitwise RBAC with category- and channel-level overrides
|
||||
- Role-based permissions: bitwise RBAC with category- and channel-level overrides
|
||||
- Customizable user sidebar layout, with personal color-coded folders that group whole spaces
|
||||
- Space discovery (public, request-to-join, and private)
|
||||
- Shareable invite codes
|
||||
@@ -132,8 +132,8 @@ You own the server, the data, and the network it federates into.
|
||||
- Mutual friends and mutual spaces
|
||||
- User profiles with banner, bio, and accent color
|
||||
- Presence and rich activities (playing, listening, watching, streaming, custom)
|
||||
- Manual status — Online, Idle, or Do Not Disturb — with a custom status message
|
||||
- Privacy controls — toggle discoverability and activity-status sharing
|
||||
- Manual status (Online, Idle, or Do Not Disturb) with a custom status message
|
||||
- Privacy controls: toggle discoverability and activity-status sharing
|
||||
|
||||
### Moderation
|
||||
- Bans with reason and moderator attribution (who, why, and when)
|
||||
@@ -144,7 +144,7 @@ You own the server, the data, and the network it federates into.
|
||||
### Federation
|
||||
- Multi-instance peering with HMAC-signed server-to-server requests
|
||||
- Federated identity resolution (`username@instance`)
|
||||
- Cross-instance DMs — messages, reactions, and membership relay
|
||||
- Cross-instance DMs: messages, reactions, and membership relay
|
||||
- Cross-instance friends and presence
|
||||
- File replication with size validation
|
||||
- Background workers for outbox delivery, file download, peer health, and cleanup
|
||||
@@ -152,27 +152,27 @@ You own the server, the data, and the network it federates into.
|
||||
### Platform
|
||||
- File uploads with image thumbnails (via `sharp`), drag-and-drop and paste-to-upload, and in-app avatar/banner cropping
|
||||
- Message search with `from:`, `has:`, `before:`, and `after:` filters, plus jump-to-message
|
||||
- Admin panel — instance settings, user management, registration controls, storage management, and federation/peering, plus granular streaming controls (a per-resolution × per-frame-rate bitrate matrix, min/max caps, quality-slider step, and an optional user-set-bitrate mode)
|
||||
- Admin panel: instance settings, user management, registration controls, storage management, and federation/peering, plus granular streaming controls (a per-resolution by per-frame-rate bitrate matrix, min/max caps, quality-slider step, and an optional user-set-bitrate mode)
|
||||
- Automatic SQLite backups (pre-migration, scheduled, and manual) with restore tooling
|
||||
- Electron desktop app (Windows, macOS, Linux) with global keybinds (push-to-talk, mute, deafen) and activity detection
|
||||
- Native desktop notifications and unread badge counts
|
||||
- Mobile-responsive web UI with a dedicated touch layout (bottom navigation, swipe gestures, full-screen views)
|
||||
- Installable PWA — add it to your phone's home screen to run it as a standalone app, with service-worker caching and an offline message queue (messages send once you reconnect)
|
||||
- Account management — password change and account deletion with safeguards
|
||||
- Installable PWA: add it to your phone's home screen to run it as a standalone app, with service-worker caching and an offline message queue (messages send once you reconnect)
|
||||
- Account management: password change and account deletion with safeguards
|
||||
|
||||
## Installation
|
||||
|
||||
The intended way to deploy Backspace is the **interactive installer** — it
|
||||
The intended way to deploy Backspace is the **interactive installer**. It
|
||||
configures everything (`.env`, secrets, HTTPS, optional voice) and brings the
|
||||
stack up for you. It **auto-detects your environment** and picks one of three
|
||||
deployment modes — the default "All-in-One" (below) needs nothing but a host and
|
||||
deployment modes. The default "All-in-One" (below) needs nothing but a host and
|
||||
a domain, but if ports 80/443 are already taken (an existing reverse proxy, a
|
||||
tunnel, another app) the installer steers you to the right mode instead of
|
||||
dead-ending. See [Deployment modes](#deployment-modes) for the full picture.
|
||||
|
||||
By default the installer **pulls a prebuilt multi-architecture image** from the
|
||||
GitHub Container Registry (`linux/amd64` + `linux/arm64`), so weak or ARM boxes
|
||||
(a Raspberry Pi) skip the heavy local build — it falls back to building from
|
||||
(a Raspberry Pi) skip the heavy local build. It falls back to building from
|
||||
source automatically if the image can't be pulled.
|
||||
|
||||
### Requirements
|
||||
@@ -181,7 +181,7 @@ source automatically if the image can't be pulled.
|
||||
- A **domain name** for your instance. In the default All-in-One mode it must
|
||||
point at the host's public IP (Caddy obtains HTTPS certificates for it
|
||||
automatically); behind your own reverse proxy or a tunnel it points at that
|
||||
edge instead — see [Deployment modes](#deployment-modes).
|
||||
edge instead. See [Deployment modes](#deployment-modes).
|
||||
- The ability to open the firewall ports in step 2 (All-in-One), or a reverse
|
||||
proxy / tunnel already terminating HTTPS for you.
|
||||
|
||||
@@ -203,34 +203,34 @@ The installer walks you through everything interactively:
|
||||
|
||||
### 2. Open the firewall ports
|
||||
|
||||
Open these on the host — and, if it's behind a router, port-forward them to the host:
|
||||
Open these on the host (and, if it's behind a router, port-forward them to the host):
|
||||
|
||||
| Port | Proto | When | Purpose |
|
||||
|------|-------|------|---------|
|
||||
| `80` | TCP | **Always** | HTTP — Caddy's automatic-HTTPS (ACME) challenge + redirect to HTTPS |
|
||||
| `443` | TCP | **Always** | HTTPS — web app, REST API, WebSocket, and LiveKit signaling (proxied) |
|
||||
| `3478` | UDP | If voice enabled | TURN — NAT traversal for WebRTC |
|
||||
| `80` | TCP | **Always** | HTTP. Caddy's automatic-HTTPS (ACME) challenge + redirect to HTTPS |
|
||||
| `443` | TCP | **Always** | HTTPS. Web app, REST API, WebSocket, and LiveKit signaling (proxied) |
|
||||
| `3478` | UDP | If voice enabled | TURN. NAT traversal for WebRTC |
|
||||
| `7881` | TCP | If voice enabled | WebRTC TCP fallback (clients that can't use UDP) |
|
||||
| `50000–60000` | UDP | If voice enabled | WebRTC media (voice / video / screen-share streams) |
|
||||
|
||||
Without voice, you only need `80` and `443`. The voice ports are required only
|
||||
when you enable LiveKit. LiveKit's own signaling port (`7880`) stays internal —
|
||||
it's reverse-proxied through Caddy on `443`, so you do **not** forward it.
|
||||
when you enable LiveKit. LiveKit's own signaling port (`7880`) stays internal.
|
||||
It's reverse-proxied through Caddy on `443`, so you do **not** forward it.
|
||||
|
||||
> **Do this together with DNS, ideally before (or right after) running the
|
||||
> installer.** Caddy gets your HTTPS certificate from Let's Encrypt the first
|
||||
> time the stack starts, which requires your domain to resolve to this host
|
||||
> **and** ports `80`/`443` reachable from the internet. If they aren't ready
|
||||
> yet, that's fine — Caddy keeps retrying, and HTTPS comes up automatically once
|
||||
> yet, that's fine. Caddy keeps retrying, and HTTPS comes up automatically once
|
||||
> DNS and the ports are in place.
|
||||
|
||||
### 3. Create your admin account
|
||||
|
||||
Open `https://your-domain` and register. **The first account created becomes the
|
||||
instance admin** — there is no default username or password.
|
||||
instance admin**. There is no default username or password.
|
||||
|
||||
If the page doesn't load over HTTPS, it's almost always DNS or ports `80`/`443`
|
||||
not being reachable from outside — check `docker compose logs caddy` for
|
||||
not being reachable from outside. Check `docker compose logs caddy` for
|
||||
certificate errors. (The installer's health check confirms the app is up
|
||||
internally, not that the certificate was issued.)
|
||||
|
||||
@@ -244,7 +244,7 @@ backup/restore and image-pinning guide.
|
||||
### Manual setup (advanced, optional)
|
||||
|
||||
The installer above is the supported path. If you'd rather configure everything
|
||||
by hand, you can skip it and drive Docker Compose directly — but then DNS,
|
||||
by hand, you can skip it and drive Docker Compose directly, but then DNS,
|
||||
`.env`, secrets, voice config, and the same firewall ports from step 2 are your
|
||||
responsibility:
|
||||
|
||||
@@ -265,7 +265,7 @@ The stack runs three services via Docker Compose:
|
||||
|-------------|---------------------------------------------------|
|
||||
| `backspace` | The app (API + WebSocket + built web client) on internal port `3000` |
|
||||
| `caddy` | Reverse proxy with automatic HTTPS for your `DOMAIN` (ports `80`/`443`) |
|
||||
| `livekit` | Voice/video server — optional, enabled with `COMPOSE_PROFILES=voice` |
|
||||
| `livekit` | Voice/video server; optional, enabled with `COMPOSE_PROFILES=voice` |
|
||||
|
||||
## Deployment modes
|
||||
|
||||
@@ -276,15 +276,15 @@ non-interactive install (`DEPLOY_MODE=proxy ./install.sh`).
|
||||
|
||||
| Mode | When | HTTPS handled by | Voice |
|
||||
|------|------|------------------|-------|
|
||||
| **`allinone`** (default) | Ports 80/443 are free and you have a domain | The bundled **Caddy** (automatic Let's Encrypt) | ✅ with UDP media ports open |
|
||||
| **`proxy`** | You already run a reverse proxy (nginx, Traefik, Caddy, Nginx Proxy Manager, SWAG…) | **Your** reverse proxy | ✅ if you also proxy `/livekit` and open the media ports |
|
||||
| **`tunnel`** | You expose the box through a tunnel (Cloudflare Tunnel, Tailscale…) | The **tunnel** provider | ❌ WebRTC/UDP can't traverse a tunnel |
|
||||
| **`allinone`** (default) | Ports 80/443 are free and you have a domain | The bundled **Caddy** (automatic Let's Encrypt) | Yes, with UDP media ports open |
|
||||
| **`proxy`** | You already run a reverse proxy (nginx, Traefik, Caddy, Nginx Proxy Manager, SWAG…) | **Your** reverse proxy | Yes, if you also proxy `/livekit` and open the media ports |
|
||||
| **`tunnel`** | You expose the box through a tunnel (Cloudflare Tunnel, Tailscale…) | The **tunnel** provider | No, WebRTC/UDP can't traverse a tunnel |
|
||||
|
||||
In `proxy` and `tunnel` mode the bundled Caddy is **not** started; instead the app
|
||||
is published on **`127.0.0.1:APP_PORT`** (loopback only — never exposed directly)
|
||||
is published on **`127.0.0.1:APP_PORT`** (loopback only, never exposed directly)
|
||||
for your proxy or tunnel to forward to. This is driven by a small overlay,
|
||||
`docker-compose.proxy.yml`, which the installer wires in for you by setting
|
||||
`COMPOSE_FILE=docker-compose.yml:docker-compose.proxy.yml` in `.env` — so every
|
||||
`COMPOSE_FILE=docker-compose.yml:docker-compose.proxy.yml` in `.env`, so every
|
||||
later `docker compose …` command in the directory keeps working with no `-f`
|
||||
flags. The installer auto-picks a free `APP_PORT` (3000/8080 are often taken);
|
||||
override it with `APP_PORT=…`.
|
||||
@@ -292,7 +292,7 @@ override it with `APP_PORT=…`.
|
||||
The installer prints ready-to-paste config for your mode at the end. The
|
||||
canonical snippets are below.
|
||||
|
||||
### Mode 2 — behind your own reverse proxy
|
||||
### Mode 2: behind your own reverse proxy
|
||||
|
||||
The app answers plain HTTP on `127.0.0.1:APP_PORT`; your proxy terminates TLS and
|
||||
forwards to it. Every snippet already includes the three things people get wrong:
|
||||
@@ -302,7 +302,7 @@ scheme/IP), and a **body-size limit** matching `MAX_UPLOAD_SIZE` (default 100 MB
|
||||
|
||||
Replace `chat.example.com` and `8080` with your domain and `APP_PORT`.
|
||||
|
||||
**nginx** — the `map` goes in `http { }` once; the `server` block per site:
|
||||
**nginx.** The `map` goes in `http { }` once; the `server` block per site:
|
||||
|
||||
```nginx
|
||||
map $http_upgrade $connection_upgrade { default upgrade; '' close; }
|
||||
@@ -330,7 +330,7 @@ server {
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
|
||||
# Voice only — forward LiveKit signaling (strips the /livekit prefix):
|
||||
# Voice only: forward LiveKit signaling (strips the /livekit prefix):
|
||||
# location /livekit/ {
|
||||
# proxy_pass http://127.0.0.1:7880/;
|
||||
# proxy_http_version 1.1;
|
||||
@@ -341,7 +341,7 @@ server {
|
||||
}
|
||||
```
|
||||
|
||||
**Caddy** (if you run your own — it handles WebSocket and `X-Forwarded-*` itself):
|
||||
**Caddy** (if you run your own; it handles WebSocket and `X-Forwarded-*` itself):
|
||||
|
||||
```caddy
|
||||
chat.example.com {
|
||||
@@ -354,7 +354,7 @@ chat.example.com {
|
||||
}
|
||||
```
|
||||
|
||||
**Traefik** (file provider — Traefik handles WebSocket automatically):
|
||||
**Traefik** (file provider; Traefik handles WebSocket automatically):
|
||||
|
||||
```yaml
|
||||
http:
|
||||
@@ -369,7 +369,7 @@ http:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://127.0.0.1:8080"
|
||||
# Voice only — add a higher-priority router + stripPrefix middleware for
|
||||
# Voice only: add a higher-priority router + stripPrefix middleware for
|
||||
# PathPrefix(`/livekit`) → http://127.0.0.1:7880.
|
||||
```
|
||||
|
||||
@@ -382,9 +382,9 @@ by hand. In **Nginx Proxy Manager**, add a **Proxy Host**:
|
||||
|-------|-------|
|
||||
| **Domain Names** | `chat.example.com` |
|
||||
| **Scheme** | `http` |
|
||||
| **Forward Hostname / IP** | `127.0.0.1` — but **if NPM runs in Docker**, `127.0.0.1` is NPM's *own* container. Use the host's LAN IP, or `host.docker.internal` with `extra_hosts: ["host.docker.internal:host-gateway"]` on the NPM container. |
|
||||
| **Forward Hostname / IP** | `127.0.0.1`, but **if NPM runs in Docker**, `127.0.0.1` is NPM's *own* container. Use the host's LAN IP, or `host.docker.internal` with `extra_hosts: ["host.docker.internal:host-gateway"]` on the NPM container. |
|
||||
| **Forward Port** | your `APP_PORT` (e.g. `8080`) |
|
||||
| **Websockets Support** | **ON** (required — chat/live events break without it) |
|
||||
| **Websockets Support** | **ON** (required; chat/live events break without it) |
|
||||
| **Block Common Exploits** | fine to leave on |
|
||||
| **SSL tab** | request a Let's Encrypt cert and enable **Force SSL** |
|
||||
| **Advanced tab** | add `client_max_body_size 100m;` (match `MAX_UPLOAD_SIZE`) |
|
||||
@@ -392,7 +392,7 @@ by hand. In **Nginx Proxy Manager**, add a **Proxy Host**:
|
||||
The same three ideas apply to any GUI proxy: forward to the app's host+port,
|
||||
enable WebSocket support, and raise the request-body limit.
|
||||
|
||||
### Mode 3 — behind a tunnel (Cloudflare, Tailscale…)
|
||||
### Mode 3: behind a tunnel (Cloudflare, Tailscale)
|
||||
|
||||
Same loopback publish as Mode 2, but the tunnel daemon on the host reaches
|
||||
`127.0.0.1:APP_PORT` and no inbound ports are opened at all. For **Cloudflare
|
||||
@@ -419,33 +419,33 @@ Two tunnel-specific caveats, both handled by the installer:
|
||||
the 100 MB default would let large uploads fail *at the edge*. In `tunnel` mode
|
||||
the installer sets `MAX_UPLOAD_SIZE=94371840` (90 MB) with headroom. Don't raise
|
||||
it back above ~100 MB behind Cloudflare.
|
||||
- **No voice.** Voice/video is **WebRTC over UDP**, which a tunnel can't carry — so
|
||||
- **No voice.** Voice/video is **WebRTC over UDP**, which a tunnel can't carry, so
|
||||
it's disabled in `tunnel` mode. If you need voice, use Mode 2 (reverse proxy)
|
||||
with the media ports opened, or All-in-One.
|
||||
|
||||
### Voice per mode
|
||||
|
||||
Voice/video (LiveKit) needs its **UDP media ports** reachable from clients —
|
||||
these carry the actual audio/video and never pass through your HTTP proxy or
|
||||
Voice/video (LiveKit) needs its **UDP media ports** reachable from clients.
|
||||
These carry the actual audio/video and never pass through your HTTP proxy or
|
||||
tunnel:
|
||||
|
||||
| Port | Proto | Purpose |
|
||||
|------|-------|---------|
|
||||
| `3478` | UDP | TURN — WebRTC NAT traversal |
|
||||
| `3478` | UDP | TURN. WebRTC NAT traversal |
|
||||
| `7881` | TCP | WebRTC TCP fallback |
|
||||
| `50000–60000` | UDP | WebRTC media (voice / video / screen-share) |
|
||||
|
||||
- **All-in-One** — voice works once those ports are open/forwarded. LiveKit
|
||||
- **All-in-One.** Voice works once those ports are open/forwarded. LiveKit
|
||||
*signaling* is proxied through Caddy on 443 (`/livekit`); port `7880` stays
|
||||
internal, never forwarded.
|
||||
- **Reverse proxy** — you must **also** route `/livekit` to `127.0.0.1:7880` (see
|
||||
- **Reverse proxy.** You must **also** route `/livekit` to `127.0.0.1:7880` (see
|
||||
the commented lines in the snippets) **and** open the media ports above.
|
||||
- **Tunnel** — voice does **not** work (UDP can't traverse the tunnel). This is a
|
||||
- **Tunnel.** Voice does **not** work (UDP can't traverse the tunnel). This is a
|
||||
known, unavoidable limitation, not a misconfiguration.
|
||||
|
||||
### Updating a running instance
|
||||
|
||||
Back up first — the app auto-snapshots the SQLite DB, and you can take one on
|
||||
Back up first. The app auto-snapshots the SQLite DB, and you can take one on
|
||||
demand with `./backup.sh` (see [`docs/systems/deployment.md`](docs/systems/deployment.md)).
|
||||
Then, from the install directory:
|
||||
|
||||
@@ -460,12 +460,12 @@ docker compose up -d --build
|
||||
```
|
||||
|
||||
Because `COMPOSE_FILE` lives in `.env`, these commands automatically use the
|
||||
right compose files in every mode — no `-f` flags to remember. A redeploy
|
||||
right compose files in every mode, with no `-f` flags to remember. A redeploy
|
||||
briefly restarts the `backspace` container (clients reconnect automatically).
|
||||
|
||||
## Development
|
||||
|
||||
Requirements: **Node.js 20 (LTS)** and **pnpm 10** — both are pinned (`.nvmrc` +
|
||||
Requirements: **Node.js 20 (LTS)** and **pnpm 10**, both pinned (`.nvmrc` plus
|
||||
the `packageManager` field), so `nvm use` and Corepack select the right versions
|
||||
automatically. Newer Node majors are untested; the Docker image always builds on
|
||||
Node 20 regardless of your host.
|
||||
@@ -478,11 +478,11 @@ pnpm dev # API server on :3005, Vite dev server on :5173
|
||||
|
||||
> **Server/web only?** `pnpm install` also builds the desktop app's native
|
||||
> keyboard-hook module (`uiohook-napi`), which needs a C++ toolchain
|
||||
> (`make`, `g++`, `python3`). If those are missing it now **warns and continues** —
|
||||
> the server and web client don't need it. Install a build toolchain
|
||||
> (`make`, `g++`, `python3`). If those are missing it now **warns and continues**,
|
||||
> and the server and web client don't need it. Install a build toolchain
|
||||
> (Debian/Ubuntu: `sudo apt install build-essential python3`) only if you're
|
||||
> building the **desktop** app. And to *self-host*, use the Docker installer
|
||||
> above — it never touches the desktop package.
|
||||
> above; it never touches the desktop package.
|
||||
|
||||
Run the halves separately if you prefer:
|
||||
|
||||
@@ -506,22 +506,22 @@ The most important:
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------------------|----------|-------------|-------------|
|
||||
| `DOMAIN` | yes | — | Public domain name of your instance |
|
||||
| `JWT_SECRET` | yes | — | Auth signing secret, **min 32 chars** (`openssl rand -hex 32`) |
|
||||
| `DEPLOY_MODE` | no | `allinone` | `allinone` \| `proxy` \| `tunnel` — see [Deployment modes](#deployment-modes) |
|
||||
| `DOMAIN` | yes | none | Public domain name of your instance |
|
||||
| `JWT_SECRET` | yes | none | Auth signing secret, **min 32 chars** (`openssl rand -hex 32`) |
|
||||
| `DEPLOY_MODE` | no | `allinone` | `allinone` \| `proxy` \| `tunnel`, see [Deployment modes](#deployment-modes) |
|
||||
| `APP_PORT` | no | auto | `proxy`/`tunnel` only: host loopback port the app is published on |
|
||||
| `PORT` | no | `3000` | App listen port (behind Caddy in Docker) |
|
||||
| `HOST` | no | `0.0.0.0` | Bind address |
|
||||
| `REGISTRATION_OPEN` | no | `true` | Set `false` to close signups after setup |
|
||||
| `MAX_UPLOAD_SIZE` | no | `104857600` | Max upload size in bytes (100 MB; 90 MB in `tunnel` mode) |
|
||||
| `BACKSPACE_IMAGE` / `BACKSPACE_IMAGE_TAG` | no | `ghcr.io/thezwiss/backspace` / `latest` | Prebuilt image to pull; pin a tag or point at your fork's registry |
|
||||
| `LIVEKIT_URL` / `LIVEKIT_API_KEY` / `LIVEKIT_API_SECRET` | no | — | Enable voice/video |
|
||||
| `COMPOSE_PROFILES` | no | — | Set to `voice` to start the bundled LiveKit service |
|
||||
| `LIVEKIT_URL` / `LIVEKIT_API_KEY` / `LIVEKIT_API_SECRET` | no | none | Enable voice/video |
|
||||
| `COMPOSE_PROFILES` | no | none | Set to `voice` to start the bundled LiveKit service |
|
||||
|
||||
## Voice & Video
|
||||
|
||||
Voice, video, and screen sharing require a [LiveKit](https://livekit.io/) server.
|
||||
The Docker Compose file bundles one — enable it by setting these in `.env`:
|
||||
The Docker Compose file bundles one. Enable it by setting these in `.env`:
|
||||
|
||||
```bash
|
||||
COMPOSE_PROFILES=voice
|
||||
@@ -531,10 +531,10 @@ LIVEKIT_API_SECRET=your-api-secret
|
||||
```
|
||||
|
||||
Enabling voice also requires opening the WebRTC ports (`3478/UDP`, `7881/TCP`,
|
||||
`50000–60000/UDP`) — see [Open the firewall ports](#2-open-the-firewall-ports).
|
||||
`50000–60000/UDP`). See [Open the firewall ports](#2-open-the-firewall-ports).
|
||||
|
||||
Without LiveKit configured, everything else — text, federation, DMs, uploads,
|
||||
search — works fully; only voice/video channels won't connect.
|
||||
Without LiveKit configured, everything else works fully (text, federation, DMs,
|
||||
uploads, search); only voice/video channels won't connect.
|
||||
|
||||
## Federation
|
||||
|
||||
@@ -558,11 +558,11 @@ Grab the installer for your platform from the
|
||||
|
||||
| Platform | File | Notes |
|
||||
|----------|------|-------|
|
||||
| Windows | `Backspace-<version>.exe` | Universal installer (x64 + arm64). SmartScreen may warn on first run — choose "More info" → "Run anyway". Auto-updates. |
|
||||
| macOS | `Backspace-<version>-arm64.dmg` (Apple Silicon) / `Backspace-<version>-x64.dmg` (Intel) | Builds are currently **unsigned**: on first launch, right-click the app → **Open** → **Open**. Auto-update is not available on macOS yet — check the releases page for new versions. |
|
||||
| Windows | `Backspace-<version>.exe` | Universal installer (x64 + arm64). SmartScreen may warn on first run; choose "More info" → "Run anyway". Auto-updates. |
|
||||
| macOS | `Backspace-<version>-arm64.dmg` (Apple Silicon) / `Backspace-<version>-x64.dmg` (Intel) | Builds are currently **unsigned**: on first launch, right-click the app → **Open** → **Open**. Auto-update is not available on macOS yet, so check the releases page for new versions. |
|
||||
| Linux | `Backspace-<version>-x86_64.AppImage` / `-arm64.AppImage`, or `.deb` (`amd64` / `arm64`) | AppImage auto-updates; `.deb` installs update via new releases. |
|
||||
|
||||
On first launch the app asks for your instance URL — enter the address of the
|
||||
On first launch the app asks for your instance URL. Enter the address of the
|
||||
Backspace server you use (e.g. `https://chat.example.com`).
|
||||
|
||||
### Building from source
|
||||
@@ -579,14 +579,14 @@ Cross-platform builds are produced for Windows, macOS, and Linux. See
|
||||
|
||||
## Mobile
|
||||
|
||||
Backspace works on mobile today — just open your instance in a phone browser.
|
||||
Backspace works on mobile today. Just open your instance in a phone browser.
|
||||
The UI has a dedicated touch layout (bottom navigation, swipe gestures, and
|
||||
full-screen views), and because it ships as an installable **PWA** you can use
|
||||
your browser's **Add to Home Screen** to install it as a standalone app: its own
|
||||
icon, no browser chrome, and an offline message queue that flushes when you
|
||||
reconnect.
|
||||
|
||||
Native **iOS and Android app-store apps are planned** — once the project gains
|
||||
Native **iOS and Android app-store apps are planned**, once the project gains
|
||||
traction and the funding for the developer-program licenses is secured. Until
|
||||
then, the installable PWA is the supported way to run Backspace on a phone.
|
||||
|
||||
@@ -596,10 +596,10 @@ Backspace is a TypeScript monorepo managed with pnpm workspaces.
|
||||
|
||||
```
|
||||
packages/
|
||||
shared/ — Shared types, permission bits, constants
|
||||
server/ — Fastify API + WebSocket server, Drizzle/SQLite, federation
|
||||
web/ — React 18 SPA (Vite, Tailwind, Zustand)
|
||||
desktop/ — Electron wrapper
|
||||
shared/ - Shared types, permission bits, constants
|
||||
server/ - Fastify API + WebSocket server, Drizzle/SQLite, federation
|
||||
web/ - React 18 SPA (Vite, Tailwind, Zustand)
|
||||
desktop/ - Electron wrapper
|
||||
```
|
||||
|
||||
| Layer | Technology |
|
||||
@@ -614,26 +614,61 @@ packages/
|
||||
| Deployment | Docker Compose + Caddy (auto-HTTPS) |
|
||||
|
||||
Every subsystem has a dedicated specification under
|
||||
[`docs/systems/`](docs/systems/) — database schema, REST API, WebSocket
|
||||
[`docs/systems/`](docs/systems/): database schema, REST API, WebSocket
|
||||
protocol, federation, permissions, voice, the design system, and more. **These
|
||||
are the reference for how Backspace works**; start there if you want to
|
||||
understand or extend a subsystem.
|
||||
|
||||
## FAQ
|
||||
|
||||
**Is Backspace a self-hosted Discord alternative?**
|
||||
Yes. It gives you a Discord-style experience (spaces, channels, roles, voice,
|
||||
video, screen sharing, DMs, friends) that you run entirely on your own server, so
|
||||
you own the data and set the rules.
|
||||
|
||||
**How is it different from Revolt, Spacebar, Matrix, or Mumble?**
|
||||
See the full [comparison](docs/comparison.md), including where each of those is the
|
||||
better choice. In short: Backspace pairs a Discord-style client with a serious
|
||||
voice and screen-share control surface and optional server-to-server federation.
|
||||
|
||||
**Does it have screen sharing and high-quality video?**
|
||||
Yes. Screen sharing goes up to 4K/120fps within admin-set bounds, with per-stream
|
||||
codec, bitrate, and resolution controls, RNNoise noise suppression, and a live
|
||||
connection inspector. Voice and video use [LiveKit](https://livekit.io/) and are
|
||||
optional; text, federation, DMs, and everything else run fully without them.
|
||||
|
||||
**Can I self-host it on a Raspberry Pi?**
|
||||
Yes. The installer pulls a prebuilt multi-architecture image (amd64 and arm64), so
|
||||
low-power and ARM boxes skip the heavy local build.
|
||||
|
||||
**Is it really open source?**
|
||||
Yes, under the GNU AGPL-3.0. A separate commercial license is available for cases
|
||||
the AGPL does not fit. Every released version stays available under the AGPL.
|
||||
|
||||
**Does it work on mobile?**
|
||||
Yes, as an installable PWA with a dedicated touch layout. Native iOS and Android
|
||||
apps are planned.
|
||||
|
||||
**What does "federation" mean here?**
|
||||
Independent Backspace instances can peer with each other so users on different
|
||||
servers can be friends, DM, and call across instances, while each server stays
|
||||
independently owned. Requests between servers are HMAC-authenticated.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please read [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
||||
first. Backspace is a single-owner project, so all contributors sign a
|
||||
[Contributor License Agreement](CLA.md) — a one-time comment on your pull
|
||||
[Contributor License Agreement](CLA.md), a one-time comment on your pull
|
||||
request, handled automatically by a bot. **You keep the copyright to your
|
||||
contribution** and grant the maintainer (Jannis Braun) an exclusive license to
|
||||
it — which is what lets Backspace be offered under both the AGPL and a commercial
|
||||
it, which is what lets Backspace be offered under both the AGPL and a commercial
|
||||
license. You also receive a perpetual license to reuse the specific code you
|
||||
wrote in your own other projects.
|
||||
|
||||
## Security
|
||||
|
||||
If you discover a security vulnerability, please **do not** open a public issue.
|
||||
Report it privately via a GitHub security advisory on this repository — see
|
||||
Report it privately via a GitHub security advisory on this repository. See
|
||||
[`SECURITY.md`](SECURITY.md). We'll work with you on a fix and coordinated
|
||||
disclosure.
|
||||
|
||||
@@ -644,23 +679,23 @@ Backspace is **free and open source software**, licensed under the
|
||||
|
||||
In plain terms:
|
||||
|
||||
- ✅ Self-host, run, study, and modify it — including commercially and inside a business.
|
||||
- ✅ Redistribute it and your changes under the same AGPL-3.0 license.
|
||||
- ⚠️ If you run a **modified** version as a network service, you must offer your
|
||||
users its complete corresponding source (AGPL § 13). Backspace makes this easy —
|
||||
- Yes: self-host, run, study, and modify it, including commercially and inside a business.
|
||||
- Yes: redistribute it and your changes under the same AGPL-3.0 license.
|
||||
- Note: if you run a **modified** version as a network service, you must offer your
|
||||
users its complete corresponding source (AGPL § 13). Backspace makes this easy:
|
||||
set `BACKSPACE_SOURCE_URL` to your fork so the in-app "Source code" link points
|
||||
at what you actually run.
|
||||
- ⚠️ Preserve the copyright and license notices.
|
||||
- Note: preserve the copyright and license notices.
|
||||
|
||||
**Commercial license.** If the AGPL doesn't fit — embedding Backspace in a
|
||||
**Commercial license.** If the AGPL doesn't fit (embedding Backspace in a
|
||||
closed-source product, offering it as a managed service without publishing your
|
||||
modifications, or an organization that can't use AGPL software — a separate
|
||||
modifications, or an organization that can't use AGPL software), a separate
|
||||
commercial license is available on request. See
|
||||
[`LICENSE-COMMERCIAL.md`](LICENSE-COMMERCIAL.md).
|
||||
|
||||
> **Our open-source commitment.** Every released version of Backspace is, and
|
||||
> will remain, available under the AGPL-3.0. The Contributor License Agreement
|
||||
> exists to enable a commercial license and optional enterprise add-ons — **not**
|
||||
> exists to enable a commercial license and optional enterprise add-ons, **not**
|
||||
> to take the open-source edition private. If this project is ever abandoned, or
|
||||
> the open-source edition is relicensed under non-free terms, the community stays
|
||||
> free to fork the last AGPL release.
|
||||
|
||||
Reference in New Issue
Block a user