mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 22:37:28 +00:00
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:
parent
4be87440d5
commit
8adafaf515
6 changed files with 1570 additions and 2 deletions
19
README.md
19
README.md
|
|
@ -33,6 +33,7 @@ plugins around one shared account system; the full product plan is in
|
|||
| `agent@` chat (configurable agent backend) + finger | ✅ |
|
||||
| M2 — admin console (`admin@`: users, sessions, moderation, plugins) | ✅ |
|
||||
| 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) | ⬜ |
|
||||
| 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).
|
||||
|
||||
### 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
|
||||
|
||||
- **Go + charmbracelet** — `wish` SSH server, `bubbletea` TUIs, `lipgloss` styling.
|
||||
|
|
|
|||
48
deploy/ergo/auth-script.sh
Normal file
48
deploy/ergo/auth-script.sh
Normal 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
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
42
deploy/ergo/refresh-certs.sh
Executable 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
126
docs/irc.md
Normal 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
148
setup.sh
|
|
@ -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)
|
||||
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
|
||||
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' "$*"; }
|
||||
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; }
|
||||
|
|
@ -85,7 +89,7 @@ log "installing packages"
|
|||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -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 \
|
||||
tor torsocks \
|
||||
debian-keyring debian-archive-keyring apt-transport-https >/dev/null
|
||||
|
|
@ -428,6 +432,14 @@ ${DOMAIN} {
|
|||
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 path_regexp tilde ^/~([^/]+)(/.*)?\$
|
||||
handle @tilde {
|
||||
|
|
@ -477,6 +489,135 @@ ufw allow 80/tcp >/dev/null
|
|||
ufw allow 443/tcp >/dev/null
|
||||
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 ----------------------------------
|
||||
log "configuring firewall + starting agentbbs"
|
||||
ufw allow 22/tcp >/dev/null
|
||||
|
|
@ -503,9 +644,12 @@ cat <<DONE
|
|||
Web https://${DOMAIN}/ site root
|
||||
https://${DOMAIN}/~<name> a member's homepage
|
||||
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)
|
||||
Logs journalctl -u agentbbs -f
|
||||
Logs journalctl -u agentbbs -f (IRC: journalctl -u ergo -f)
|
||||
Update re-run this script (git pull + rebuild + restart)
|
||||
DONE
|
||||
warn "Before you log out: open a new terminal and confirm ssh -p ${ADMIN_SSH_PORT} <you>@${DOMAIN} works."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue