mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 14:27:27 +00:00
feat(irc): irc.profullstack.com host, OS-user (tilde.town) gate, premium channels
Hostname: serve the network as irc.profullstack.com (new IRC_DOMAIN var, default irc.<root-of-DOMAIN>). Caddy serves an irc.profullstack.com site so it gets a Let's Encrypt cert; ergo-refresh-certs copies that into Ergo for 6697. Needs an A record irc.profullstack.com -> the box (self-signed until it resolves). Members are OS users (tilde.town model): setup.sh reconciles a real OS account per member dir (root-side, on each deploy + the 15-min timer; nologin shell, so identity-only — no shell access). The IRC auth-script now gates on `getent passwd` with uid>=1000 instead of the member dir, so "OS user" == member. Premium channels: free members may /join; creating channels is a premium perk. The ssh irc@ client gains /create #name (premium-gated via ensurePremium): it joins the fresh channel and registers it with ChanServ as the member's founder. v1 gate is route-level (operator-only-creation left off); external-client creation hardening is a follow-up. See docs/irc.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d4ada98b69
commit
5eb1e96480
5 changed files with 194 additions and 87 deletions
|
|
@ -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@<host>)\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 <nick> <text> /me <action> /names /nick <name> /quit"))
|
||||
m.push(cSys.Render("commands: /join #chan /part [#chan] /create #chan /msg <nick> <text> /me <action> /names /nick <name> /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"))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue