Wire shared MOTD into BBS hub and IRC

Use profullstack.com/motd as the shared Message of the Day across both
the SSH BBS hub and the Ergo IRC server.

- internal/motd: fetch + in-memory cache with background refresh (stdlib
  only); Current() never blocks session start, keeps last value on error.
  Source overridable via AGENTBBS_MOTD_URL.
- hub: append the daily MOTD below the existing welcome/onboarding text.
- IRC: deploy/ergo/refresh-motd.sh pulls /motd into Ergo's ergo.motd and
  rehashes; setup.sh installs it + an ergo-motd.timer (hourly) mirroring
  the ergo-certs timer, with a seeded fallback if the source is offline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-06-25 11:51:06 +00:00
parent 63548bcbbe
commit fb7722eacc
4 changed files with 177 additions and 6 deletions

View file

@ -68,6 +68,7 @@ import (
"github.com/profullstack/agentbbs/internal/mail"
"github.com/profullstack/agentbbs/internal/mailbox"
"github.com/profullstack/agentbbs/internal/mailu"
"github.com/profullstack/agentbbs/internal/motd"
"github.com/profullstack/agentbbs/internal/news"
"github.com/profullstack/agentbbs/internal/payments"
"github.com/profullstack/agentbbs/internal/plugin"
@ -238,6 +239,12 @@ func main() {
}
log.Info("sandbox", "mode", a.sandbox.Mode())
// Shared Message of the Day from profullstack.com — shown on the hub (and on
// IRC via Ergo's MOTD). Cached + refreshed in the background so it never
// blocks session start. Override the source with AGENTBBS_MOTD_URL (empty
// disables remote fetch).
motd.Start(context.Background(), env("AGENTBBS_MOTD_URL", motd.DefaultURL), 30*time.Minute)
// Email confirmation endpoint (the link in the join@ verification mail).
// Loopback only; Caddy reverse-proxies /verify to it. Separate from the
// on-demand-TLS ask server above.
@ -396,15 +403,22 @@ var bannerStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#e11
func (a *app) hubMOTD(u auth.User) string {
body := env("AGENTBBS_MOTD",
"A terminal BBS for humans & AI agents.\nGames · IRC · News · a Linux pod · your own homepage.")
var s string
if u.Kind == auth.Guest {
return "You're browsing as a guest.\n" + body +
s = "You're browsing as a guest.\n" + body +
"\nssh join@" + a.host + " to claim a username, a pod & a homepage."
} else {
welcome := "Welcome back, " + u.Name + "."
if n, err := a.st.UnreadCount(u.Name); err == nil && n > 0 {
welcome += fmt.Sprintf(" 📬 %d unread — open Members ▸ inbox (i).", n)
}
s = welcome + "\n" + body
}
welcome := "Welcome back, " + u.Name + "."
if n, err := a.st.UnreadCount(u.Name); err == nil && n > 0 {
welcome += fmt.Sprintf(" 📬 %d unread — open Members ▸ inbox (i).", n)
// Append the shared daily Message of the Day from profullstack.com, if loaded.
if m := motd.Current(); m != "" {
s += "\n\n" + m
}
return welcome + "\n" + body
return s
}
// teaHandler builds the hub model for guests, members, and agents.