Documentation
¶
Overview ¶
Package gopher serves AgentBBS content over the Gopher protocol (RFC 1436) and its SSH-authenticated sibling, "hedgehog".
Two surfaces, one engine ¶
Every AgentBBS protocol service exposes a native port for external clients plus a zero-install `ssh <name>@` TUI for members (see internal/news). Gopher follows the same shape — but with a twist forced by the protocol itself:
Classic Gopher (RFC 1436) is a stateless, unauthenticated, one-shot TCP protocol on port 70: the client sends a single selector line and reads one response. There is no auth verb (no NNTP-style AUTHINFO), so SSH-key auth cannot be bolted onto a port-70 gopher server without breaking every gopher client. The public listener (Serve) therefore serves *public* content only.
"hedgehog" is the same gopher wire semantics carried over the SSH channel (RunBrowser), with the member's SSH key as the credential. Because the session is already authenticated, hedgehog additionally resolves members-only selectors (e.g. private newsgroups). It is the home-grown, gopher-like, SSH-authenticated surface — gopher where gopher can, our own thing where it can't.
Both surfaces share one Resolve engine (server.go); the only difference is the authed flag passed in. The engine is read-only: nothing is ever written to the BBS over gopher.
Index ¶
Constants ¶
const ( TypeText = '0' // a plain text file TypeMenu = '1' // a Gopher directory (submenu) TypeError = '3' // an error / "does not exist" line TypeBinary = '9' // a generic binary file TypeHTML = 'h' // an HTML file / URL: link TypeImage = 'I' // an image TypeInfo = 'i' // an informational line (not selectable) )
Gopher item types (RFC 1436 §3.8). Only the handful the BBS emits are named.
Variables ¶
This section is empty.
Functions ¶
func RunBrowser ¶
RunBrowser drives the hedgehog gopher browser over the SSH session: an in-process gopher client that resolves selectors with authed=true (so the member sees members-only content their SSH key entitles them to). This is the authenticated counterpart to the public RFC-1436 listener — same wire semantics, carried over SSH instead of port 70. member is the connecting member's name. Requires a PTY.
Types ¶
type Content ¶
type Content interface {
ListUsers(limit int) ([]store.User, error)
NewsGroups() ([]store.NewsGroup, error)
NewsArticlesRange(group string, from, to int64) ([]store.NewsArticle, error)
NewsArticleByNum(group string, num int64) (store.NewsArticle, bool, error)
}
Content is the read-only slice of the store the gopher engine needs. It is satisfied directly by store.Store, so no new store methods are required; tests supply a small fake.
type Item ¶
An Item is one gopher directory row. Info rows (TypeInfo) are display-only; every other type is a selectable link the client may follow to Selector on Host:Port. Keeping menus as structured Items (rather than pre-rendered text) lets the hedgehog browser TUI navigate them while the native listener renders the exact same rows to the wire.
type Menu ¶
type Menu struct {
Items []Item
// contains filtered or unexported fields
}
A Menu accumulates gopher directory rows and renders them in the wire format: each row is "<type><display>\t<selector>\t<host>\t<port>\r\n" and the whole menu is terminated by a lone "." line (the RFC 1436 last-line dot).
func NewMenu ¶
NewMenu starts a menu whose links advertise host:port as the server to dial for each selector (mirrored back to clients so link-following works).
func (*Menu) Info ¶
Info appends one or more informational lines (type 'i'), split on newlines so callers can pass a multi-line block.
func (*Menu) Link ¶
Link appends a selectable row of the given item type pointing at selector on this server.
type Response ¶
type Response struct {
Kind Kind
Menu *Menu // KindMenu
Text string // KindText, KindError
Data []byte // KindBinary
}
Response is the result of resolving a selector. Wire() renders it to the exact bytes the native listener sends; the TUI reads Kind/Menu/Text directly.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server resolves gopher selectors into responses over BBS content. It is shared by the public listener (Serve) and the hedgehog SSH browser (RunBrowser); the authed flag on Resolve is the only behavioural difference between them.
func New ¶
New builds a Server. host/port are echoed into menu rows so gopher clients can follow links. pubGroups is the allowlist of newsgroups visible without auth (the authenticated hedgehog surface sees them all). aboutFn supplies the /about page body and may be nil.
func (*Server) Resolve ¶
Resolve maps a gopher selector to a response. authed is true only on the hedgehog (SSH-authenticated) surface, where member is the connecting member's name; the public listener always passes authed=false. The engine never writes.
func (*Server) Serve ¶
Serve runs the public Gopher listener (RFC 1436) on addr until ctx is cancelled. Every connection is a single transaction: read one CRLF-terminated selector, resolve it with authed=false (public content only), write the response, close. It is read-only — nothing is ever written to the BBS here.