feat(irc): members-only Ergo IRC network co-located on the BBS

Provision a self-hosted Ergo IRC network (irc.${DOMAIN}) in setup.sh §9b:
single Go binary on its own ports/user, reusing Caddy's Let's Encrypt cert
for 6697 TLS (refreshed by a timer; self-signed fallback on first boot),
loopback 6667 + a loopback WebSocket fronted by Caddy at wss://${DOMAIN}/irc.

Access is MEMBERS-ONLY: every client must authenticate with SASL, self-service
registration is off, and an auth-script (deploy/ergo/auth-script.sh, installed
as /usr/local/bin/ergo-auth-member) approves a login only if the account name
maps to an existing AgentBBS member home dir under <data>/users/. Passphrase is
ignored — membership (the filesystem dir) is the credential. require-sasl has
no IP exemption so WebSocket clients (which reach Ergo via Caddy from 127.0.0.1)
can't bypass the gate; accounts are auto-created on first successful auth.

Public attack surface is TLS-only (ufw opens 6697; 6667 is loopback). Toggle
with IRC=0. See docs/irc.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-06-14 11:21:39 +00:00
parent 4be87440d5
commit 8adafaf515
6 changed files with 1570 additions and 2 deletions

View file

@ -33,6 +33,7 @@ plugins around one shared account system; the full product plan is in
| `agent@` chat (configurable agent backend) + finger | ✅ | | `agent@` chat (configurable agent backend) + finger | ✅ |
| M2 — admin console (`admin@`: users, sessions, moderation, plugins) | ✅ | | M2 — admin console (`admin@`: users, sessions, moderation, plugins) | ✅ |
| M3 — AgentGames (`game@` + WebSocket; TTT/C4, ELO ladder, replays) | ✅ | | M3 — AgentGames (`game@` + WebSocket; TTT/C4, ELO ladder, replays) | ✅ |
| IRC (`irc.bbs.profullstack.com` — Ergo network for humans + agents) | ✅ |
| M4 — Files (cl1.tech SFTP workspaces) | ⬜ | | M4 — Files (cl1.tech SFTP workspaces) | ⬜ |
| M5 — AgentAd marketplace (built on the AgentAd standard in logicsrc) | ⬜ | | M5 — AgentAd marketplace (built on the AgentAd standard in logicsrc) | ⬜ |
@ -99,6 +100,24 @@ The production host (`bbs.profullstack.com`) is provisioned by the idempotent
Full details, required secrets, and ops commands: [`docs/deploy.md`](docs/deploy.md). Full details, required secrets, and ops commands: [`docs/deploy.md`](docs/deploy.md).
### IRC network
`setup.sh` also stands up a co-located [Ergo](https://ergo.chat) IRC server (its
own `ergo.service`, ports 6697/TLS + a Caddy-fronted WebSocket) so humans and
agents can meet on a real IRC network. It is **members-only**: every client must
authenticate with SASL, and an auth-script approves a login only if the account
name is an existing AgentBBS member (registration is off — your BBS account *is*
your IRC identity):
```bash
# native client — SASL account = your BBS member name
/connect irc.bbs.profullstack.com 6697
# browser / agent over WebSocket
wss://bbs.profullstack.com/irc
```
Set `IRC=0` to skip it. Full details: [`docs/irc.md`](docs/irc.md).
## Architecture ## Architecture
- **Go + charmbracelet**`wish` SSH server, `bubbletea` TUIs, `lipgloss` styling. - **Go + charmbracelet**`wish` SSH server, `bubbletea` TUIs, `lipgloss` styling.

View file

@ -0,0 +1,48 @@
#!/usr/bin/env bash
#
# auth-script.sh — Ergo auth-script that gates the IRC network on AgentBBS
# membership. setup.sh installs this to /usr/local/bin/ergo-auth-member and
# wires it into /etc/ergo/ircd.yaml (accounts.auth-script).
#
# "Member" == a user with a home dir under the AgentBBS users dir (created when
# someone registers via `ssh join@`). IRC is members-only, so a login is
# approved iff the requested account name maps to such a dir. The passphrase is
# intentionally IGNORED — membership (a filesystem dir) IS the credential, by
# design (see docs/irc.md). Anyone who knows a member's name can connect as
# them; that tradeoff was chosen deliberately for this private, TLS-only network.
#
# Protocol (Ergo): one JSON object on stdin per attempt, one JSON line on stdout
# then exit. Input keys: accountName, passphrase, certfp, ip. Output:
# {"success":bool,"accountName":str,"error":str}.
#
# args: ["<users-dir>"] # defaults to /var/lib/agentbbs/users
set -uo pipefail
USERS_DIR="${1:-/var/lib/agentbbs/users}"
# Always emit valid JSON and exit 0 — Ergo reads the JSON, not the exit code;
# a non-zero exit / no output is treated as a script error, not a clean deny.
deny() { printf '{"success":false,"error":"%s"}\n' "${1:-not a member}"; exit 0; }
# Don't gate on read's exit code: a final line without a trailing newline still
# carries data (read returns non-zero at EOF but populates $line).
line=""
read -r line || true
[ -n "$line" ] || deny "no input"
acct="$(printf '%s' "$line" | jq -r '.accountName // ""' 2>/dev/null || true)"
# certfp-only attempts carry no account name; we don't support cert auth here.
[ -n "$acct" ] || deny "membership requires an account name"
# Defense in depth against path traversal. IRC account names are a restricted
# charset anyway, but never let one escape USERS_DIR.
case "$acct" in
*[!A-Za-z0-9._-]* | "." | ".." | *..* | */* ) deny "invalid account name" ;;
esac
if [ -d "$USERS_DIR/$acct" ]; then
printf '{"success":true,"accountName":"%s"}\n' "$acct"
else
deny "not a member"
fi

1189
deploy/ergo/ircd.yaml Normal file

File diff suppressed because it is too large Load diff

42
deploy/ergo/refresh-certs.sh Executable file
View file

@ -0,0 +1,42 @@
#!/usr/bin/env bash
#
# refresh-certs.sh — copy Caddy's Let's Encrypt cert for $DOMAIN into Ergo's
# TLS dir and reload Ergo if it changed. setup.sh installs this to
# /usr/local/bin/ergo-refresh-certs and runs it from the ergo-certs.timer so
# the IRC server's 6697 cert tracks Caddy's auto-renewals.
#
# Ergo and Caddy share the same hostname (${DOMAIN}); Caddy is the only ACME
# client on the box, so we reuse its cert rather than running a second ACME
# client. Exits non-zero (without touching anything) if Caddy hasn't issued the
# cert yet — on first boot that's expected, and setup.sh falls back to a
# self-signed cert until this timer picks up the real one.
set -euo pipefail
DOMAIN="${DOMAIN:?set DOMAIN}"
ERGO_DATA="${ERGO_DATA:-/var/lib/ergo}"
CADDY_DATA="${CADDY_DATA:-/var/lib/caddy/.local/share/caddy}"
# Caddy stores certs under certificates/<acme-dir>/<host>/<host>.{crt,key};
# the ACME directory segment varies (prod vs staging), so glob for it.
crt="$(ls "$CADDY_DATA"/certificates/*/"$DOMAIN"/"$DOMAIN".crt 2>/dev/null | head -1 || true)"
key="$(ls "$CADDY_DATA"/certificates/*/"$DOMAIN"/"$DOMAIN".key 2>/dev/null | head -1 || true)"
if [ -z "$crt" ] || [ -z "$key" ]; then
echo "no Caddy cert for $DOMAIN yet (looked under $CADDY_DATA/certificates)"
exit 1
fi
dst="$ERGO_DATA/tls"
install -d -m 0755 "$dst"
changed=0
if ! cmp -s "$crt" "$dst/fullchain.pem"; then install -m 0644 "$crt" "$dst/fullchain.pem"; changed=1; fi
if ! cmp -s "$key" "$dst/privkey.pem"; then install -m 0640 "$key" "$dst/privkey.pem"; changed=1; fi
chown -R ergo:ergo "$dst" 2>/dev/null || true
if [ "$changed" = 1 ]; then
echo "updated Ergo TLS cert for $DOMAIN"
# Ergo rehashes config + reloads certs on SIGHUP (systemctl reload).
systemctl reload ergo 2>/dev/null || systemctl restart ergo 2>/dev/null || true
else
echo "Ergo TLS cert for $DOMAIN already current"
fi

126
docs/irc.md Normal file
View file

@ -0,0 +1,126 @@
# IRC — `irc.bbs.profullstack.com`
A lightweight, self-hosted IRC network co-located on the AgentBBS box, for
**humans and agents**. It runs [Ergo](https://ergo.chat) (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 shares the box and the `bbs.profullstack.com` TLS cert with the BBS but runs
as its **own service on its own ports** (`ergo.service`, user `ergo`), so it is
operationally independent of the wish server.
## Connect
| Path | Address | For |
|---|---|---|
| Native TLS | `irc.bbs.profullstack.com:6697` (TLS) | desktop/CLI clients (HexChat, irssi, WeeChat, Halloy…) |
| WebSocket | `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/bridges; 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.
### Membership (who can connect)
The network is **members-only**. There is **no self-service registration**
every client must authenticate with SASL, and a login is approved only if the
account name is an existing AgentBBS member, i.e. someone who has registered via
`ssh join@bbs.profullstack.com` (which creates their home dir under
`/var/lib/agentbbs/users/<name>/`). Non-members are refused at connect.
Authenticate with SASL using **your BBS username as the account name**. The
passphrase is **ignored** — membership (the filesystem home dir) *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 gate is Ergo's `auth-script` (`/usr/local/bin/ergo-auth-member`, from
[`deploy/ergo/auth-script.sh`](../deploy/ergo/auth-script.sh)) with
`accounts.require-sasl` on and `accounts.registration` off. On first successful
login the Ergo account is auto-created (`autocreate`), so members never register.
> 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.
### 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_NETWORK` in `setup.sh`)
- **Server name:** `irc.bbs.profullstack.com`
- Access: **members-only** (SASL required; account = BBS member, see [Membership](#membership-who-can-connect))
- Self-service account registration: **off**
- Message history: **in-memory**, ~7-day window, `CHATHISTORY` enabled
## Operating it
It is provisioned by [`../setup.sh`](../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`](../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 and already holds a valid cert for
`bbs.profullstack.com`. 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.
> Native clients connect to **`irc.bbs.profullstack.com`**, so make sure that
> hostname resolves to the box (an A record, or a CNAME to `bbs.profullstack.com`).
> The TLS cert is for `bbs.profullstack.com`; if you want a clean match on the
> `irc.` hostname, add it as a SAN to the Caddy site or use a wildcard cert.
### Config knobs (`setup.sh` env)
| Var | Default | Meaning |
|---|---|---|
| `IRC` | `1` | install the IRC server (`0` to skip/disable) |
| `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. This is the BBS hosting **its own** IRC network for people and
agents to meet on.
## Ideas / next steps
- **In-BBS `irc@` route** — an SSH route that drops a member straight into the
local network (mirroring `tor-irc@` but pointed at `127.0.0.1:6667`), so
`ssh irc@bbs.profullstack.com` is an instant client with no setup.
- **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.mysql` on if replay must survive
restarts.

148
setup.sh
View file

@ -38,6 +38,10 @@ SKIP_BUILD="${SKIP_BUILD:-0}" # set 1 to use prebuilt /usr/local/bin/{agen
SWAP_SIZE="${SWAP_SIZE:-3G}" # swapfile size added on low-RAM hosts (set 0 to skip) SWAP_SIZE="${SWAP_SIZE:-3G}" # swapfile size added on low-RAM hosts (set 0 to skip)
SELF_UPDATE="${SELF_UPDATE:-1}" # set 0 to skip the autonomous self-update systemd timer SELF_UPDATE="${SELF_UPDATE:-1}" # set 0 to skip the autonomous self-update systemd timer
SELF_UPDATE_INTERVAL="${SELF_UPDATE_INTERVAL:-15min}" # how often the box polls origin for new commits SELF_UPDATE_INTERVAL="${SELF_UPDATE_INTERVAL:-15min}" # how often the box polls origin for new commits
IRC="${IRC:-1}" # set 0 to skip the co-located Ergo IRC server (irc.${DOMAIN})
ERGO_VERSION="${ERGO_VERSION:-2.18.0}" # Ergo IRCd release to install
IRC_NETWORK="${IRC_NETWORK:-ProfullstackBBS}" # IRC network name shown to clients
ERGO_DATA="${ERGO_DATA:-/var/lib/ergo}" # Ergo state dir (ircd.db, tls/)
log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; } warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; }
@ -85,7 +89,7 @@ log "installing packages"
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
apt-get update -qq apt-get update -qq
apt-get install -y -qq \ apt-get install -y -qq \
git ca-certificates curl ufw ffmpeg unzip \ git ca-certificates curl ufw ffmpeg unzip jq \
podman uidmap slirp4netns fuse-overlayfs \ podman uidmap slirp4netns fuse-overlayfs \
tor torsocks \ tor torsocks \
debian-keyring debian-archive-keyring apt-transport-https >/dev/null debian-keyring debian-archive-keyring apt-transport-https >/dev/null
@ -428,6 +432,14 @@ ${DOMAIN} {
reverse_proxy http://${HTTP_ADDR} reverse_proxy http://${HTTP_ADDR}
} }
# IRC over WebSocket: Caddy terminates TLS and proxies to Ergo's loopback
# WebSocket listener, so web clients hit wss://${DOMAIN}/irc and agents get a
# WebSocket transport without exposing another public port. (No-op if IRC=0;
# Ergo just isn't listening on 8097, so /irc returns 502.)
handle /irc {
reverse_proxy 127.0.0.1:8097
}
# tilde.town-style homepages: /~name[/path] -> users/name/public_html/path # tilde.town-style homepages: /~name[/path] -> users/name/public_html/path
@tilde path_regexp tilde ^/~([^/]+)(/.*)?\$ @tilde path_regexp tilde ^/~([^/]+)(/.*)?\$
handle @tilde { handle @tilde {
@ -477,6 +489,135 @@ ufw allow 80/tcp >/dev/null
ufw allow 443/tcp >/dev/null ufw allow 443/tcp >/dev/null
systemctl reload caddy 2>/dev/null || systemctl restart caddy systemctl reload caddy 2>/dev/null || systemctl restart caddy
# ---- 9b. Ergo IRC server (co-located irc.${DOMAIN}; humans + agents) --------
# A lightweight single-binary IRC network on its own ports, sharing this box and
# this hostname's TLS cert. Native clients hit irc.${DOMAIN}:6697 (TLS); web
# clients and agents hit wss://${DOMAIN}/irc (Caddy fronts Ergo's loopback
# WebSocket). See docs/irc.md. Disable with IRC=0.
if [ "$IRC" = "1" ]; then
log "installing Ergo IRC server v${ERGO_VERSION} (irc.${DOMAIN})"
case "$GOARCH" in
amd64) ERGO_ARCH=x86_64 ;;
arm64) ERGO_ARCH=arm64 ;;
*) ERGO_ARCH="$GOARCH" ;;
esac
id ergo >/dev/null 2>&1 || useradd --system --home-dir "$ERGO_DATA" --shell /usr/sbin/nologin ergo
install -d -m 0755 /opt/ergo "$ERGO_DATA" "$ERGO_DATA/tls" /etc/ergo
# Install/upgrade the binary + bundled languages (idempotent: only on version change).
if [ "$(/opt/ergo/ergo --version 2>/dev/null)" != "ergo-${ERGO_VERSION}" ]; then
tmp="$(mktemp -d)"
curl -fsSL "https://github.com/ergochat/ergo/releases/download/v${ERGO_VERSION}/ergo-${ERGO_VERSION}-linux-${ERGO_ARCH}.tar.gz" -o "$tmp/ergo.tgz" \
|| die "could not download Ergo ${ERGO_VERSION}"
tar -C "$tmp" -xzf "$tmp/ergo.tgz"
d="$tmp/ergo-${ERGO_VERSION}-linux-${ERGO_ARCH}"
install -m 0755 "$d/ergo" /opt/ergo/ergo
rm -rf /opt/ergo/languages && cp -r "$d/languages" /opt/ergo/languages
rm -rf "$tmp"
fi
# Operator password: generate once, keep the plaintext root-only, embed only the hash.
if [ ! -f "$ENV_DIR/ergo-oper.txt" ]; then
OPER_PASS="$(head -c18 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c24)"
printf '%s\n' "$OPER_PASS" > "$ENV_DIR/ergo-oper.txt"
chmod 600 "$ENV_DIR/ergo-oper.txt"
fi
OPER_PASS="$(cat "$ENV_DIR/ergo-oper.txt")"
OPER_HASH="$(printf '%s\n%s\n' "$OPER_PASS" "$OPER_PASS" | /opt/ergo/ergo genpasswd 2>/dev/null | tail -1)"
# Render the config template from the repo (the __TOKENS__ become real values).
sed -e "s|__NETWORK__|${IRC_NETWORK}|g" \
-e "s|__DOMAIN__|${DOMAIN}|g" \
-e "s|__DATA__|${ERGO_DATA}|g" \
-e "s|__TLS_DIR__|${ERGO_DATA}/tls|g" \
-e "s|__LANG_DIR__|/opt/ergo/languages|g" \
-e "s|__OPER_PASSWORD_HASH__|${OPER_HASH}|g" \
-e "s|__USERS_DIR__|${DATA_DIR}/users|g" \
"${SRC_DIR}/deploy/ergo/ircd.yaml" > /etc/ergo/ircd.yaml
chmod 640 /etc/ergo/ircd.yaml
# IRC is members-only: this auth-script approves a SASL login only if the
# account name maps to an AgentBBS member home dir under ${DATA_DIR}/users.
install -m 0755 "${SRC_DIR}/deploy/ergo/auth-script.sh" /usr/local/bin/ergo-auth-member
# TLS for 6697: reuse Caddy's Let's Encrypt cert for ${DOMAIN}; self-signed
# fallback on first run before Caddy has issued it (the timer swaps it in).
install -m 0755 "${SRC_DIR}/deploy/ergo/refresh-certs.sh" /usr/local/bin/ergo-refresh-certs
DOMAIN="$DOMAIN" ERGO_DATA="$ERGO_DATA" /usr/local/bin/ergo-refresh-certs || true
if [ ! -s "$ERGO_DATA/tls/fullchain.pem" ]; then
warn "no Caddy cert for ${DOMAIN} yet — using a self-signed cert on 6697 until the ergo-certs timer swaps in the real one"
( cd /etc/ergo && /opt/ergo/ergo mkcerts --conf /etc/ergo/ircd.yaml --quiet 2>/dev/null ) \
|| openssl req -newkey rsa:2048 -nodes -days 90 -x509 \
-keyout "$ERGO_DATA/tls/privkey.pem" -out "$ERGO_DATA/tls/fullchain.pem" \
-subj "/CN=irc.${DOMAIN}" 2>/dev/null
fi
chown -R ergo:ergo "$ERGO_DATA" /etc/ergo
# Initialize the datastore once.
[ -f "$ERGO_DATA/ircd.db" ] || sudo -u ergo /opt/ergo/ergo initdb --conf /etc/ergo/ircd.yaml --quiet
log "installing ergo.service"
cat > /etc/systemd/system/ergo.service <<UNIT
[Unit]
Description=Ergo IRC server (AgentBBS — irc.${DOMAIN})
After=network-online.target
Wants=network-online.target
[Service]
User=ergo
Group=ergo
WorkingDirectory=/opt/ergo
ExecStart=/opt/ergo/ergo run --conf /etc/ergo/ircd.yaml
# Ergo rehashes config + reloads TLS certs on SIGHUP.
ExecReload=/bin/kill -HUP \$MAINPID
Restart=always
RestartSec=2
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=${ERGO_DATA} /etc/ergo
[Install]
WantedBy=multi-user.target
UNIT
# Daily cert refresh from Caddy (tracks auto-renewals).
cat > /etc/systemd/system/ergo-certs.service <<UNIT
[Unit]
Description=Refresh Ergo TLS cert from Caddy for ${DOMAIN}
[Service]
Type=oneshot
Environment=DOMAIN=${DOMAIN}
Environment=ERGO_DATA=${ERGO_DATA}
ExecStart=/usr/local/bin/ergo-refresh-certs
UNIT
cat > /etc/systemd/system/ergo-certs.timer <<UNIT
[Unit]
Description=Periodic Ergo TLS cert refresh from Caddy
[Timer]
OnBootSec=5min
OnUnitActiveSec=12h
Persistent=true
[Install]
WantedBy=timers.target
UNIT
systemctl daemon-reload
systemctl enable ergo >/dev/null 2>&1 || true
systemctl restart ergo
systemctl enable --now ergo-certs.timer >/dev/null 2>&1 || true
ufw allow 6697/tcp >/dev/null
sleep 1
systemctl is-active --quiet ergo \
|| warn "ergo failed to start — check: journalctl -u ergo -n50"
else
systemctl disable --now ergo ergo-certs.timer >/dev/null 2>&1 || true
fi
# ---- 10. firewall + start agentbbs on :22 ---------------------------------- # ---- 10. firewall + start agentbbs on :22 ----------------------------------
log "configuring firewall + starting agentbbs" log "configuring firewall + starting agentbbs"
ufw allow 22/tcp >/dev/null ufw allow 22/tcp >/dev/null
@ -503,9 +644,12 @@ cat <<DONE
Web https://${DOMAIN}/ site root Web https://${DOMAIN}/ site root
https://${DOMAIN}/~<name> a member's homepage https://${DOMAIN}/~<name> a member's homepage
https://<your-domain> a member's homepage on a custom domain (auto-HTTPS) https://<your-domain> a member's homepage on a custom domain (auto-HTTPS)
IRC irc.${DOMAIN}:6697 (TLS) native clients ${IRC:+(set IRC=0 to disable)}
wss://${DOMAIN}/irc web clients + agents over WebSocket
/OPER admin <pw> oper password in ${ENV_DIR}/ergo-oper.txt
Config ${ENV_DIR}/agentbbs.env (set CoinPay + LiveKit, then: systemctl restart agentbbs) Config ${ENV_DIR}/agentbbs.env (set CoinPay + LiveKit, then: systemctl restart agentbbs)
Logs journalctl -u agentbbs -f Logs journalctl -u agentbbs -f (IRC: journalctl -u ergo -f)
Update re-run this script (git pull + rebuild + restart) Update re-run this script (git pull + rebuild + restart)
DONE DONE
warn "Before you log out: open a new terminal and confirm ssh -p ${ADMIN_SSH_PORT} <you>@${DOMAIN} works." warn "Before you log out: open a new terminal and confirm ssh -p ${ADMIN_SSH_PORT} <you>@${DOMAIN} works."