diff --git a/deploy/ergo/auth-script.sh b/deploy/ergo/auth-script.sh index 77f4332..e45d74a 100644 --- a/deploy/ergo/auth-script.sh +++ b/deploy/ergo/auth-script.sh @@ -4,21 +4,22 @@ # 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. +# AgentBBS members are real OS users (tilde.town model; setup.sh provisions an +# OS account per member). IRC is members-only, so a login is approved iff the +# requested account name is a real OS user with uid >= MIN_UID (which excludes +# system accounts like root/ergo/agentbbs). The passphrase is intentionally +# IGNORED — membership (being an OS user) 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: [""] # defaults to /var/lib/agentbbs/users +# args: [""] # defaults to 1000 set -uo pipefail -USERS_DIR="${1:-/var/lib/agentbbs/users}" +MIN_UID="${1:-1000}" # 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. @@ -35,14 +36,22 @@ 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. +# Restrict to plain login names (defense in depth; IRC names are limited anyway). case "$acct" in - *[!A-Za-z0-9._-]* | "." | ".." | *..* | */* ) deny "invalid account name" ;; + *[!A-Za-z0-9._-]* | "." | ".." ) deny "invalid account name" ;; esac -if [ -d "$USERS_DIR/$acct" ]; then +# Resolve the OS account; getent passwd returns name:passwd:uid:gid:... +entry="$(getent passwd "$acct" 2>/dev/null || true)" +[ -n "$entry" ] || deny "not a member" + +uid="$(printf '%s' "$entry" | cut -d: -f3)" +case "$uid" in + ''|*[!0-9]*) deny "not a member" ;; +esac + +if [ "$uid" -ge "$MIN_UID" ]; then printf '{"success":true,"accountName":"%s"}\n' "$acct" else - deny "not a member" + deny "system accounts cannot use IRC" fi diff --git a/deploy/ergo/ircd.yaml b/deploy/ergo/ircd.yaml index 9d68282..8d500ed 100644 --- a/deploy/ergo/ircd.yaml +++ b/deploy/ergo/ircd.yaml @@ -6,11 +6,11 @@ # # __NETWORK__ network name shown to clients (e.g. ProfullstackBBS) # __DOMAIN__ the BBS domain (e.g. bbs.profullstack.com) +# __IRC_DOMAIN__ the IRC server name + TLS cert host (e.g. irc.profullstack.com) # __DATA__ Ergo state dir (ircd.db lives here) # __TLS_DIR__ dir holding fullchain.pem / privkey.pem for 6697 # __LANG_DIR__ Ergo's bundled languages/ dir (from the release) # __OPER_PASSWORD_HASH__ bcrypt hash for /OPER admin (ergo genpasswd) -# __USERS_DIR__ AgentBBS member home dirs (the IRC membership gate) # # It is based on Ergo's upstream default.yaml (v2.18.0) with the AgentBBS # listeners (public 6697 TLS, loopback 6667, loopback 8097 WebSocket fronted @@ -18,15 +18,16 @@ # for a mixed humans + agents network: it 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 __USERS_DIR__. Message history (CHATHISTORY) is enabled so +# approves a login only if the account name is a real OS user (uid>=1000). +# BBS members are provisioned as OS users (tilde.town model; setup.sh §4b/§9a2), +# so "OS user" == "BBS member". Message history (CHATHISTORY) is enabled so # reconnecting agents and web clients can replay. See docs/irc.md. # # Most settings keep Ergo's recommended defaults — read the inline comments # before changing one. A few worth knowing about: # 1. network.name / server.name — the network identity (rendered from tokens). # 2. server.listeners — the 6697 cert/key are refreshed from Caddy's Let's -# Encrypt cert for __DOMAIN__ by setup.sh (self-signed fallback on first run). +# Encrypt cert for __IRC_DOMAIN__ by setup.sh (self-signed fallback on first run). # 3. opers — the /OPER admin password hash (rendered from a token). # 4. history — in-memory, messages expire after ~7 days. Switch to MySQL-backed # persistent history (datastore.mysql) if you need durability across restarts. @@ -39,13 +40,13 @@ network: # server configuration server: # server name - name: irc.__DOMAIN__ + name: __IRC_DOMAIN__ # addresses to listen on listeners: # Public TLS (6697) — the front door for native IRC clients (humans) and # SASL-authenticating agents. Cert/key are refreshed from Caddy's - # Let's Encrypt cert for __DOMAIN__ by setup.sh (falls back to self-signed). + # Let's Encrypt cert for __IRC_DOMAIN__ by setup.sh (falls back to self-signed). ":6697": tls: cert: __TLS_DIR__/fullchain.pem @@ -597,12 +598,12 @@ accounts: # see the manual for details on how to write an authentication plugin script auth-script: # MEMBERS-ONLY gate: ergo-auth-member approves a login iff the account - # name maps to an existing AgentBBS member home dir (passed as the arg). + # name is a real OS user with uid >= the arg (BBS members are OS users). enabled: true command: "/usr/local/bin/ergo-auth-member" - # the AgentBBS users dir is passed as a constant arg; the per-attempt - # auth data (accountName/passphrase/ip) is sent over stdin/stdout: - args: ["__USERS_DIR__"] + # min-uid is passed as a constant arg (excludes system accounts like + # ergo/root); the per-attempt auth data is sent over stdin/stdout: + args: ["1000"] # auto-create the Ergo account on first successful (member) auth, so # members never have to register: autocreate: true diff --git a/docs/irc.md b/docs/irc.md index 74d6afe..3a7b03b 100644 --- a/docs/irc.md +++ b/docs/irc.md @@ -1,21 +1,21 @@ -# IRC — `irc.bbs.profullstack.com` +# IRC — `irc.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. +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.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 | +| 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 @@ -30,33 +30,56 @@ 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`, `/msg `, `/me`, `/names`, `/nick`, `/help`, and -`esc` to leave. Override the target with `AGENTBBS_IRC_ADDR` on a dev host. +`/join #chan`, `/part [#chan]`, `/create #chan` (premium), `/msg `, +`/me`, `/names`, `/nick`, `/help`, and `esc` to leave. Override the target with +`AGENTBBS_IRC_ADDR` on a dev host. -### Membership (who can connect) +### Membership (who can connect) — members are OS users -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//`). Non-members are refused at connect. +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`](../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** — 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. +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. Because +> `channels.operator-only-creation` is 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 is `ssh irc@`, the +> route-level gate is the intended v1. + ### Connect as an agent Agents authenticate with **SASL PLAIN** using their member account name (any @@ -77,9 +100,10 @@ Any standard IRC library works — e.g. `irc-framework` (Node), `pydle` / ## 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)) +- **Server name:** `irc.profullstack.com` (`IRC_DOMAIN` in `setup.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, `CHATHISTORY` enabled ## Operating it @@ -100,23 +124,24 @@ the same self-update timer as the BBS. Toggle with `IRC=0`. ### 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. +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. -> 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. +> **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.` (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 | diff --git a/internal/irc/tui.go b/internal/irc/tui.go index ffae9b5..9123adf 100644 --- a/internal/irc/tui.go +++ b/internal/irc/tui.go @@ -24,18 +24,20 @@ var ( ) // Run drives the IRC TUI over the SSH session until the member leaves; leaving -// ends the session (irc@ is a dedicated route, like agent@). -func Run(s ssh.Session, c *Client) error { +// ends the session (irc@ is a dedicated route, like agent@). canCreate gates the +// /create command on premium membership. +func Run(s ssh.Session, c *Client, canCreate bool) error { ptyReq, winCh, hasPty := s.Pty() if !hasPty { _, _ = s.Write([]byte("irc needs a terminal (ssh -t irc@)\r\n")) return nil } m := &model{ - c: c, - channel: DefaultChannel, - width: ptyReq.Window.Width, - height: ptyReq.Window.Height, + c: c, + channel: DefaultChannel, + canCreate: canCreate, + width: ptyReq.Window.Width, + height: ptyReq.Window.Height, } m.lines = append(m.lines, cSys.Render(fmt.Sprintf("connected as %s — joined %s. /help for commands, esc to leave.", c.Nick(), DefaultChannel))) @@ -62,10 +64,11 @@ func waitEvent(c *Client) tea.Cmd { } type model struct { - c *Client - channel string // current conversation target for typed lines - lines []string - input string + c *Client + channel string // current conversation target for typed lines + canCreate bool // premium members may /create (register) new channels + lines []string + input string width, height int } @@ -159,7 +162,27 @@ func (m *model) command(text string) tea.Cmd { arg := strings.TrimSpace(strings.TrimPrefix(text, fields[0])) switch cmd { case "/help": - m.push(cSys.Render("commands: /join #chan /part [#chan] /msg /me /names /nick /quit")) + m.push(cSys.Render("commands: /join #chan /part [#chan] /create #chan /msg /me /names /nick /quit")) + case "/create": + if !m.canCreate { + m.push(cErr.Render("creating channels is a Founding Lifetime Member perk — upgrade with: ssh join@ (the BBS). You can still /join existing channels.")) + break + } + ch := arg + if ch == "" { + m.push(cErr.Render("usage: /create #channel")) + break + } + if !strings.HasPrefix(ch, "#") { + ch = "#" + ch + } + // operator-only-creation is off, so joining a fresh channel creates it and + // ops the creator; registering it with ChanServ makes it persist with you + // as founder. (Both run in order on this connection.) + _ = m.c.Join(ch) + _ = m.c.Privmsg("ChanServ", "REGISTER "+ch) + m.channel = ch + m.push(cSys.Render("created " + ch + " — you're the founder. Share the name so members can /join it.")) case "/join": if arg == "" { m.push(cErr.Render("usage: /join #channel")) diff --git a/setup.sh b/setup.sh index 14cb169..e8f586f 100755 --- a/setup.sh +++ b/setup.sh @@ -38,7 +38,8 @@ 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}) +IRC="${IRC:-1}" # set 0 to skip the co-located Ergo IRC server (${IRC_DOMAIN}) +IRC_DOMAIN="${IRC_DOMAIN:-irc.${DOMAIN#*.}}" # IRC host (default: irc., e.g. irc.profullstack.com) NEWS="${NEWS:-1}" # set 0 to skip the co-located Usenet/NNTP server (news.${DOMAIN}) ERGO_VERSION="${ERGO_VERSION:-2.18.0}" # Ergo IRCd release to install IRC_NETWORK="${IRC_NETWORK:-ProfullstackBBS}" # IRC network name shown to clients @@ -481,6 +482,26 @@ news.${DOMAIN} { " fi +# IRC site (${IRC_DOMAIN}). Caddy serving this host means it obtains a Let's +# Encrypt cert for it — which ergo-refresh-certs copies into Ergo for 6697 TLS. +# It also fronts the same loopback WebSocket, so wss://${IRC_DOMAIN}/irc works +# (alongside wss://${DOMAIN}/irc). Needs an A record ${IRC_DOMAIN} -> this host. +IRC_SITE="" +if [ "$IRC" = "1" ]; then + IRC_SITE=" +${IRC_DOMAIN} { + encode zstd gzip + handle /irc { + reverse_proxy 127.0.0.1:8097 + } + handle { + header Content-Type \"text/plain; charset=utf-8\" + respond \"AgentBBS IRC (members-only). Native: ${IRC_DOMAIN}:6697 (TLS), SASL as your BBS name. Web/agents: wss://${IRC_DOMAIN}/irc. Or from the BBS: ssh -t irc@${DOMAIN}\" + } +} +" +fi + cat > /etc/caddy/Caddyfile <.${DOMAIN} (needs wildcard DNS # *.${DOMAIN} -> this host). On-demand TLS mints a cert only when agentbbs's # ask endpoint confirms is a registered member, so random subdomains @@ -558,13 +579,39 @@ 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. +# ---- 9a2. members are OS users (tilde.town model) -------------------------- +# Every BBS member gets a real OS account. It is IDENTITY ONLY — a nologin shell, +# so it grants no shell access (BBS login is the wish server on :22, authenticated +# against the sqlite store, not OpenSSH/PAM). This matches the tilde.town shape +# and is what lets the IRC network gate on `getent passwd`. The agentbbs service +# runs unprivileged and can't useradd, so we reconcile here (root) on every deploy +# + the 15-min self-update timer: create an account for any member home dir that +# lacks one. Existing members are migrated on the first run; new members get their +# OS account within one reconcile (<= ${SELF_UPDATE_INTERVAL}). +log "reconciling member OS users from ${DATA_DIR}/users" +getent group members >/dev/null 2>&1 || groupadd --system members +if [ -d "${DATA_DIR}/users" ]; then + for d in "${DATA_DIR}"/users/*/; do + [ -d "$d" ] || continue + name="$(basename "$d")" + # Only valid member/login names; skip anything already taken (incl. system users). + case "$name" in *[!a-z0-9._-]* | "" ) continue ;; esac + id "$name" >/dev/null 2>&1 && continue + # Non-system account (uid auto-assigned >=1000, matching the IRC auth-script + # gate), home = the member's existing data dir, no shell. + useradd --no-create-home --home-dir "$d" --shell /usr/sbin/nologin --gid members "$name" 2>/dev/null \ + && log " + OS user ${name}" \ + || warn " could not create OS user ${name}" + done +fi + +# ---- 9b. Ergo IRC server (co-located ${IRC_DOMAIN}; humans + agents) -------- +# A lightweight single-binary IRC network on its own ports. Native clients hit +# ${IRC_DOMAIN}:6697 (TLS, using Caddy's Let's Encrypt cert for ${IRC_DOMAIN}); +# web clients and agents hit wss://${DOMAIN}/irc or wss://${IRC_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})" + log "installing Ergo IRC server v${ERGO_VERSION} (${IRC_DOMAIN})" case "$GOARCH" in amd64) ERGO_ARCH=x86_64 ;; arm64) ERGO_ARCH=arm64 ;; @@ -597,28 +644,29 @@ if [ "$IRC" = "1" ]; then # 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|__IRC_DOMAIN__|${IRC_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. + # account name is a real OS user (uid>=1000) — i.e. a BBS member, since + # members are provisioned as OS users in §4b (tilde.town model). 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 + # TLS for 6697: reuse Caddy's Let's Encrypt cert for ${IRC_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 + DOMAIN="$IRC_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" + warn "no Caddy cert for ${IRC_DOMAIN} yet (is its A record pointed here?) — 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 + -subj "/CN=${IRC_DOMAIN}" 2>/dev/null fi chown -R ergo:ergo "$ERGO_DATA" /etc/ergo @@ -628,7 +676,7 @@ if [ "$IRC" = "1" ]; then log "installing ergo.service" cat > /etc/systemd/system/ergo.service < /etc/systemd/system/ergo-certs.service < a member's homepage https:// a member's homepage on a custom domain (auto-HTTPS) - IRC irc.${DOMAIN}:6697 (TLS) native clients ${IRC:+(set IRC=0 to disable)} + IRC ${IRC_DOMAIN}:6697 (TLS) native clients (DNS: ${IRC_DOMAIN} A -> host) ${IRC:+(set IRC=0 to disable)} wss://${DOMAIN}/irc web clients + agents over WebSocket + ssh -t irc@${DOMAIN} the in-BBS client (premium: /create #chan) /OPER admin oper password in ${ENV_DIR}/ergo-oper.txt News news.${DOMAIN}:563 (NNTPS) newsreaders + agents ${NEWS:+(set NEWS=0 to disable)} ssh -t news@${DOMAIN} the in-BBS newsreader (DNS: news.${DOMAIN} A -> host)