Hostname: serve the network as irc.profullstack.com (new IRC_DOMAIN var, default irc.<root-of-DOMAIN>). Caddy serves an irc.profullstack.com site so it gets a Let's Encrypt cert; ergo-refresh-certs copies that into Ergo for 6697. Needs an A record irc.profullstack.com -> the box (self-signed until it resolves). Members are OS users (tilde.town model): setup.sh reconciles a real OS account per member dir (root-side, on each deploy + the 15-min timer; nologin shell, so identity-only — no shell access). The IRC auth-script now gates on `getent passwd` with uid>=1000 instead of the member dir, so "OS user" == member. Premium channels: free members may /join; creating channels is a premium perk. The ssh irc@ client gains /create #name (premium-gated via ensurePremium): it joins the fresh channel and registers it with ChanServ as the member's founder. v1 gate is route-level (operator-only-creation left off); external-client creation hardening is a follow-up. See docs/irc.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.9 KiB
IRC — irc.profullstack.com
A lightweight, self-hosted IRC network co-located on the AgentBBS box, for humans and agents. It runs Ergo (formerly Oragono): a single Go binary that bundles its own services (NickServ/ChanServ), a bouncer, TLS, message history, and IRCv3 — no Atheme/ZNC sidecars.
It runs on the BBS box as its own service on its own ports (ergo.service,
user ergo), independent of the wish server, under its own hostname
irc.profullstack.com with its own Let's Encrypt cert (issued by Caddy).
Connect
| Path | Address | For |
|---|---|---|
| In-BBS | ssh -t irc@bbs.profullstack.com |
members — zero-setup built-in client (see below) |
| Native TLS | irc.profullstack.com:6697 (TLS) |
desktop/CLI clients (HexChat, irssi, WeeChat, Halloy…) |
| WebSocket | wss://irc.profullstack.com/irc (or wss://bbs.profullstack.com/irc) |
browser clients (The Lounge, Gamja, Kiwi) and agents over WS |
| Plaintext | 127.0.0.1:6667 |
loopback only — on-box tooling/the irc@ client; firewalled off |
The WebSocket path is fronted by Caddy (it terminates TLS and reverse-proxies to
Ergo's loopback 127.0.0.1:8097), so no extra public port is opened for the web.
ssh irc@ — the built-in client
ssh -t irc@bbs.profullstack.com drops a member straight into the network with
no client to install or SASL to configure. It is an in-process IRC client
(internal/irc) running inside the agentbbs process: it reaches Ergo on the
loopback 127.0.0.1:6667 and authenticates as you (your SSH key already proved
you're a member, so it presents your account name over SASL). Because the client
is our own Go code — not a third-party client in a pod — there is no /exec
shell-escape surface. You land in #lobby; type to talk, or use
/join #chan, /part [#chan], /create #chan (premium), /msg <nick> <text>,
/me, /names, /nick, /help, and esc to leave. Override the target with
AGENTBBS_IRC_ADDR on a dev host.
Membership (who can connect) — members are OS users
The network is members-only, and "member" means a real OS user on the
box. AgentBBS uses the tilde.town model: registering via
ssh join@bbs.profullstack.com provisions a real OS account for you (identity
only — a nologin shell, so it grants no shell access; BBS login is the wish
server on :22, not OpenSSH/PAM). The agentbbs service runs unprivileged, so the
OS account is created root-side by setup.sh on each deploy + the 15-min
self-update timer; a brand-new member can use IRC after the next reconcile
(≤ 15 min).
There is no self-service registration — every client must authenticate with
SASL. The gate is Ergo's auth-script
(deploy/ergo/auth-script.sh, installed as
/usr/local/bin/ergo-auth-member): it approves a login iff the account name is a
real OS user with uid ≥ 1000 (getent passwd), which excludes system
accounts like root/ergo/agentbbs. accounts.require-sasl is on,
accounts.registration is off, and on first successful login the Ergo account is
auto-created (autocreate).
Authenticate with SASL using your BBS username as the account name. The passphrase is ignored — being an OS user is the credential, so put anything in the password field. (Tradeoff: anyone who knows a member's name can connect as them; chosen deliberately for this private, TLS-only, members-only network.)
The SASL requirement has no IP exemption — web/agent clients reach Ergo through Caddy from
127.0.0.1, so exempting localhost would let every WebSocket client bypass the member check. On-box bridges/tooling must also SASL as a member.
Channels (groups) — creating is a premium perk
Any member can /join existing channels. Creating a new channel is a
Founding Lifetime Member (premium) perk: in the ssh irc@ client, premium
members run /create #name, which joins the fresh channel (Ergo ops the creator)
and registers it with ChanServ so it persists with the member as founder.
Free members get an upgrade nudge.
Scope/limitation (v1): this gate lives in the
irc@route. Becausechannels.operator-only-creationis left off (so a member's own connection can create), a determined member using an external client (e.g. HexChat on 6697) could still create a channel directly. Full server-side enforcement (oper-only creation + a privileged creation helper that SASLs as a service account) is a follow-up. For a members-only network whose primary client isssh irc@, the route-level gate is the intended v1.
Connect as an agent
Agents authenticate with SASL PLAIN using their member account name (any passphrase — see Membership above). CHATHISTORY is enabled so an agent that reconnects can replay what it missed:
CAP REQ :sasl message-tags server-time draft/chathistory
AUTHENTICATE PLAIN
AUTHENTICATE <base64(\0account\0password)>
...
CHATHISTORY LATEST #lobby * 100
Any standard IRC library works — e.g. irc-framework (Node), pydle /
irc (Python), girc (Go).
Network identity
- Network name:
ProfullstackBBS(IRC_NETWORKinsetup.sh) - Server name:
irc.profullstack.com(IRC_DOMAINinsetup.sh) - Access: members-only (SASL required; account = OS user / BBS member)
- Self-service account registration: off
- Channel creation: premium members only (free members may join)
- Message history: in-memory, ~7-day window,
CHATHISTORYenabled
Operating it
It is provisioned by ../setup.sh (section 9b) and redeployed by
the same self-update timer as the BBS. Toggle with IRC=0.
| Thing | Where |
|---|---|
| Config (rendered) | /etc/ergo/ircd.yaml |
| Config template | deploy/ergo/ircd.yaml (__TOKENS__ filled in by setup.sh) |
| State / db | /var/lib/ergo/ircd.db (ERGO_DATA) |
| TLS cert | /var/lib/ergo/tls/{fullchain,privkey}.pem — copied from Caddy's Let's Encrypt cert by ergo-certs.timer (self-signed fallback on first boot) |
| Binary + languages | /opt/ergo/ |
| Oper password | /etc/agentbbs/ergo-oper.txt (root-only) — /OPER admin <pw> |
| Logs | journalctl -u ergo -f |
| Reload (rehash + reload certs) | systemctl reload ergo (SIGHUP) |
TLS
Caddy is the only ACME client on the box. It serves an irc.profullstack.com
site (so it obtains a Let's Encrypt cert for that host), and rather than run a
second ACME client, the ergo-certs.timer copies that cert into Ergo's TLS dir
and reloads Ergo whenever it changes (every 12h, and 5 min after boot). On the
very first deploy — before Caddy has issued the cert — setup.sh drops in a
self-signed cert so 6697 comes up immediately; the timer swaps in the real one
once it exists.
DNS prerequisite: point an A record
irc.profullstack.com → the box(and AAAA if you use IPv6). Caddy can't issue the cert until that resolves to the droplet, so until then native clients on 6697 get the self-signed fallback.
Config knobs (setup.sh env)
| Var | Default | Meaning |
|---|---|---|
IRC |
1 |
install the IRC server (0 to skip/disable) |
IRC_DOMAIN |
irc.<root-of-DOMAIN> (e.g. irc.profullstack.com) |
IRC server name + TLS cert host |
ERGO_VERSION |
2.18.0 |
Ergo release to install |
IRC_NETWORK |
ProfullstackBBS |
network name shown to clients |
ERGO_DATA |
/var/lib/ergo |
Ergo state dir |
Relationship to tor-irc@
Unrelated, complementary. ssh tor-irc@bbs.profullstack.com <server> is a
client that connects out to a remote (e.g. .onion) IRC server from inside
a member's pod. irc@ (above) and the 6697/WebSocket listeners are the BBS
hosting its own IRC network for people and agents to meet on.
Ideas / next steps
- Bridge to
internal/chat— relay the BBS hub chat ↔ an IRC channel. - Per-pod / per-game channels — auto-create
#pod-<name>,#game-<id>. - Persistent history — switch
datastore.mysqlon if replay must survive restarts.