mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 22:37:28 +00:00
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:
parent
63548bcbbe
commit
fb7722eacc
4 changed files with 177 additions and 6 deletions
90
internal/motd/motd.go
Normal file
90
internal/motd/motd.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Package motd fetches the shared Message of the Day from profullstack.com and
|
||||
// caches it in memory, refreshing in the background. The hub MOTD reads the
|
||||
// cached value (never blocking session start); a failed fetch keeps the last
|
||||
// known value. Set AGENTBBS_MOTD_URL to override the source (empty disables).
|
||||
package motd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DefaultURL is the canonical shared MOTD endpoint (plain text, CORS-open).
|
||||
const DefaultURL = "https://profullstack.com/motd"
|
||||
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
cached string
|
||||
)
|
||||
|
||||
// Current returns the most recently fetched MOTD, or "" if none has been
|
||||
// fetched yet (or fetching is disabled/failing).
|
||||
func Current() string {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
return cached
|
||||
}
|
||||
|
||||
func set(s string) {
|
||||
mu.Lock()
|
||||
cached = s
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
func fetch(ctx context.Context, url string) (string, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", &httpError{resp.StatusCode}
|
||||
}
|
||||
// Cap the read; the MOTD is a short text blob.
|
||||
b, err := io.ReadAll(io.LimitReader(resp.Body, 8<<10))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(b)), nil
|
||||
}
|
||||
|
||||
type httpError struct{ code int }
|
||||
|
||||
func (e *httpError) Error() string { return "motd: unexpected status " + http.StatusText(e.code) }
|
||||
|
||||
// Start fetches the MOTD once (blocking, with a short timeout so a slow source
|
||||
// can't stall boot for long) then refreshes every `every` until ctx is done.
|
||||
// A url of "" disables fetching entirely.
|
||||
func Start(ctx context.Context, url string, every time.Duration) {
|
||||
if url == "" {
|
||||
return
|
||||
}
|
||||
refresh := func() {
|
||||
c, cancel := context.WithTimeout(ctx, 8*time.Second)
|
||||
defer cancel()
|
||||
if s, err := fetch(c, url); err == nil && s != "" {
|
||||
set(s)
|
||||
}
|
||||
}
|
||||
refresh()
|
||||
go func() {
|
||||
t := time.NewTicker(every)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue