Slircdocs Waiting list

Slirc is in invite-only pre-launch. Chat works today, and personal agents are coming soon.

Connect / Self-hosting

Run your own Slirc

Slirc runs as two containers on a private network behind Caddy. Bun serves the app, and Ergo handles chat. This guide takes you from a fresh Linux server to your first message.

Prefer hosted? We can run Slirc for you at yourdomain.slirc.org, from €29/month.

The Elastic License 2.0 lets you run Slirc for your own group. Hosting it for others requires permission from the author.

Source coming soon

The repository is private until then, so the clone step below will not work yet. Join the waiting list and tick the self-host box, and we'll email the link when the source opens.

What you need

1. Clone and set your host

On your machine:

git clone https://github.com/bitbonsai/slirc
cd slirc

Open justfile and set the SSH host, checkout path, and config directory at the top to your own values. Every recipe below uses them.

2. Config on the server

Slirc keeps secrets out of git. Two files live in the config directory on the server, both mode 0600:

ssh yourserver
mkdir -p ~/.config/slirc && chmod 700 ~/.config/slirc

~/.config/slirc/env is the app's environment:

APP_HOST=app.yourdomain
OWNER_NICK=yournick
USERS_JSON={"yournick":"you@example.com"}
IRC_PASSWORDS_JSON={"yournick":"a-long-random-password"}
ERGO_OPER_USER=slirc-org
ERGO_OPER_PASS=another-long-random-password
RESEND_API_KEY=re_...
RESEND_FROM=Slirc <slirc@yourdomain>
VAPID_CONTACT=mailto:you@example.com

Optional keys: GIPHY_API_KEY for GIPHY search, OPENROUTER_API_KEY for the help bot and natural-language reminders, which reach every model through OpenRouter, HELP_ANSWER_MODEL for the model that writes help answers (default anthropic/claude-haiku-5.5), HELP_CLASSIFIER_MODEL for the cheaper model that first sorts each message into a Slirc question, small talk, or off topic, TYPESAFE_API_KEY to do that sorting with TypeSafe's Jev (the classifier then defaults to jev-latest instead of xiaomi/mimo-v2.6-flash; TYPESAFE_BASE_URL and TYPESAFE_INPUT_PRICE_PER_BILLION are optional), HELP_MODEL for reminders and the other helpers (default xiaomi/mimo-v2.5), SLIRC_HELPERS_ENABLED=false to run without the help, remind, poll, and welcome helpers. Model names are OpenRouter ids; older bare names such as mimo-v2.5 still work. Reminder forms such as in 20m stretch, tomorrow 9:00 pick up cake, list, and cancel 3 still work without a model.

The welcome helper sits in #open-chat and posts a G'day gif there once a day when GIPHY_API_KEY is set. The gif goes through Ergo like any message, so IRC clients see it too.

~/.config/slirc/ergo.yaml is Ergo's config. Start from the one in the repo:

scp ergo/ergo.example.yaml yourserver:~/.config/slirc/ergo.yaml

Then edit two things in it. Set network.name and server.name to your domain. Replace the password hash of the slirc-org oper with one for the ERGO_OPER_PASS you chose:

podman run --rm -it ghcr.io/bitbonsai/slirc-ergo:v2.19.1 ergo genpasswd

Keep accounts.authentication-enabled: true, keep the slirc-org oper class as it is, and keep 10.0.0.0/8 in ip-limits.exempted. Every browser reaches Ergo through the app's address, so per-IP limits would lock everyone out at once.

3. Caddy

Copy caddy/slirc.caddy to your Caddy sites directory and replace slirc.org with your domain and the checkout paths with yours. The app block is the one that matters:

app.yourdomain {
    handle_path /webirc {
        reverse_proxy 127.0.0.1:8067
    }
    handle {
        reverse_proxy 127.0.0.1:9010 {
            lb_try_duration 20s
        }
    }
}

Port 8067 is Ergo's WebSocket listener, 9010 is the app. Both bind to localhost only. Reload Caddy.

4. Bootstrap

Back on your machine, with a clean tree:

just bootstrap

This pushes main, resets the server checkout to it, creates the slirc-org network and the three volumes, starts Ergo, then builds and starts the app. The app registers your account with Ergo from USERS_JSON and IRC_PASSWORDS_JSON, creates #open-chat and #help, and starts the helpers. Check it:

just status
just logs app

The first line you want in the log is app listening on :8080.

5. First sign-in

Open https://app.yourdomain, type the email from USERS_JSON, and open the link Resend delivers. That account is the owner: it can create channels, invite people, and create bots. Other people join by invitation; ordinary sign-in does not create an account for an uninvited address. See Getting started.

Day to day

just deploy          # tests, then rebuild and recreate the app; Ergo keeps running
just deploy-ergo     # recreate Ergo; the app keeps running
just deploy-docs     # publish the docs site, if you serve one
just logs ergo

deploy refuses to run if Ergo restarted underneath it, and deploy-ergo refuses if the app did.

Backups

Three things hold state:

Copy the config directory into private backup storage. Do not export a live database volume and assume the result is consistent: SQLite's WAL must be included in a transactionally consistent snapshot. Use SQLite's backup API for the app database and Ergo's SQLite history. A full Ergo account-store backup needs a separately authorized maintenance stop unless a supported live-backup method is available. With services stopped, podman volume export and podman volume import can back up and restore complete volumes.

Before upgrading, rehearse app schema migrations on a consistent snapshot, never the live database:

cd app
bun scripts/rehearse-migration.ts /private/backups/app.sqlite

The rehearsal runs migrations twice on a disposable copy, checks SQLite integrity, and verifies existing rows and account IDs stay unchanged. It does not run IRC provisioning or connect to production.

History retention

The supplied Ergo config uses history.restrictions.expire-time: 0s: persistent messages do not expire automatically. Explicit message deletion still works. Existing installations must update their mounted config separately; changing the example does not change a running server. Ergo can apply this setting by rehash without restarting. Disabling expiry does not restore messages already purged under an earlier policy. Monitor disk usage and keep backups.

When something is off