Commit graph

150 commits

Author SHA1 Message Date
cddd9819cc feat(files): web file browser at files.<host> with webmail-password login
Adds a browser-based file manager so members can use their files without an
SSH key. Served on a loopback HTTP server (AGENTBBS_FILES_WEB_ADDR, default
127.0.0.1:8092) that Caddy fronts at files.<host>. Members sign in with their
webmail username + password, verified against the Mailu IMAP backend
(mailbox.VerifyLogin), and browse the same virtual /me + /public areas as SFTP
— no home directory is ever exposed. Upload/download/mkdir/delete with the
private-area quota enforced; reuses internal/files confinement (fs.go).
setup.sh renders the files.<DOMAIN> Caddy site + env knob. Unit tests cover
the auth gate and an upload/list/download/delete round trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:09:18 +00:00
1dbb2c70e2 feat(mailbox): compose & reply in the AgentMail TUI
The interactive reader (ssh mail@ / hub Mail) was read-only; the send path
existed only for agents (bot mode). Add compose (c), reply (r), reply-all (a)
to the TUI with a To/Cc/Subject/Body form (tab/arrows to move fields, ctrl+d
send, esc cancel). Reply prefills + quotes the original and threads via
In-Reply-To. Tests drive the model key-by-key through send.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:09:18 +00:00
045acc965a fix(ergo): use absolute lock-file path so it lands in the writable data dir
WorkingDirectory is /opt/ergo with ProtectSystem=strict (only /var/lib/ergo
and /etc/ergo are writable), but lock-file was relative ("ircd.lock"), so Ergo
tried to create /opt/ergo/ircd.lock on a read-only path and crash-looped with
"failed to acquire flock on ircd.lock: read-only file system" — nothing ever
listened on 6697/6667/8097 (clients got ECONNREFUSED). datastore.path is already
absolute (__DATA__/ircd.db); per Ergo's own config comment the lock-file should
match. Render it to __DATA__/ircd.lock (/var/lib/ergo, ergo-owned + in
ReadWritePaths).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:57:49 +00:00
11503ca3f0 docs: credentials.md for git/mailbox creds, notify-creds, and SMTP ports
New docs/credentials.md covers member git-account provisioning, the
notify-creds backfill command (flags + behavior), required env, and the
two SMTP paths — transactional AGENTBBS_SMTP_* (STARTTLS :587) vs. the
gateway loopback relay AGENTBBS_MAIL_SMTP_ADDR (:25 into Mailu),
clarifying why :25 is correct and that 465 (not "467") is implicit-TLS.

Cross-linked from README ops and docs/mail.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:48:36 +00:00
ce0758b0ec
Email members their git + mailbox credentials on provisioning (#57)
* feat(files): SFTP member storage — private workspaces + shared public area + mgmt TUI

Implements M4 (Files). A fully virtual Go SFTP server (pkg/sftp + crypto/ssh,
no OS users) wired as an "sftp" subsystem on the existing :22 wish listener, so
members reach their files with their login key:

    sftp files@bbs.profullstack.com     # scp/rsync ride the same endpoint

Identity is the SSH key (the username is conventional/ignored). Two areas per
session: a private, quota-limited /me workspace and a single shared public file
area /public (old-school BBS file area; world-read, members-only write by
default, operator-moderated). This reverses the old NG1 "no sharing" boundary in
favour of one sanctioned, inspectable sharing surface (PRD §9.3 amended).

internal/files:
- backend.go  service, layout, quota/usage, live-session registry, operator API
- fs.go       per-session virtual FS; resolve() is the single security
              chokepoint (area confinement + symlink-escape guard) + pkg/sftp
              request handlers
- server.go   subsystem handler: key auth -> member session -> request server,
              with byte metering and force-disconnect
- tui.go      in-BBS member browser (hub plugin "Files")
- admin.go    operator management TUI: sessions, workspaces/quotas, public area

Operator console: ssh sftp@<host> (allowlist-gated; sftpadmin@/filesadmin@
aliases) — list/disconnect sessions, set per-user quotas, revoke SFTP access,
toggle public write, moderate the public area.

store: files_access (per-user quota override + revoked) and files_settings
(public-write mode) tables + methods. main.go wiring guarded by AGENTBBS_FILES
(+ AGENTBBS_FILES_QUOTA_MB, default 1 GiB). Route names reserved.

Tests (incl -race): path traversal/confinement, symlink-escape rejection,
public-write ACL, quota enforcement, usage accounting, and an end-to-end run
against a real SFTP client. Docs: docs/files.md; PRD §5.3/§5.3.1/§9.3 + README
updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add notify-creds subcommand to (re)email members git + mailbox creds

`agentbbs notify-creds` backfills credential emails to verified members
who signed up before the git/mailbox welcome emails existed.

- git (all verified): forgejo.EnsureUserReset resets each account to a
  fresh one-time password (must-change) and emails the web login link,
  username, and password. New method since the original one-time
  password is not recoverable for existing accounts.
- mailbox (all verified): ensures the forwardemail alias and emails the
  address + webmail link.
- Preview by default; --send executes. --git/--mail/--user filters.
  Refuses --send without SMTP; warns+skips when Forgejo/forwardemail
  are unconfigured.

Also folds in the welcome-email functions (gitWelcomeEmailBody,
mailWelcomeEmailBody, EnsureUser password return, provisionGit/
ensurePremium sends) that this builds on. README ops + forgejo tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 03:48:11 -07:00
7c794dcf79
Merge pull request #56 from profullstack/feat/files-sftp
feat(files): SFTP member storage — private workspaces + shared public area + mgmt TUI
2026-06-23 03:47:19 -07:00
95d1afb59a Merge branch 'feat/files-sftp' into feat/notify-creds
# Conflicts:
#	cmd/agentbbs/main.go
2026-06-23 10:46:00 +00:00
5b6e0070d3 docs(files): spell out access policy — members-only, free for all (not Premium-gated)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:45:22 +00:00
38e4797ee9 Merge remote-tracking branch 'origin/main' into feat/files-sftp
# Conflicts:
#	cmd/agentbbs/main.go
#	internal/auth/auth.go
#	internal/store/store.go
2026-06-23 10:42:32 +00:00
006235ce92
Feat/mail all members (#55)
* feat(mail): give every verified member a free @bbs.profullstack.com mailbox

Email was built but paid-only (Founding Lifetime gate) and never wired to a
running backend. Make it a free benefit of membership and split the address
domain from the mail-server host.

- internal/mailu: Mailu admin-API client; EnsureUser idempotently provisions a
  mailbox via the loopback admin REST API (token = mailu.env API_TOKEN).
- main.go: auto-provision <name>@<mailDomain> at join@ verification and on first
  Mail open; un-gate the Mail hub entry + mail@ (membership/email-verified, not
  Premium); address domain (AGENTBBS_MAIL_ADDR_DOMAIN, default the BBS host) is
  now distinct from the mail server host (AGENTBBS_MAIL_DOMAIN) and the webmail
  URL. Drop the forwardemail alias path (Mailu now owns delivery for everyone).
- mailbox: gate on membership (a registered handle) instead of Paid;
  ErrNotPaid -> ErrNotMember.
- join@ copy: list email under free membership; premium now pitches custom
  domains + Tor only.
- setup.sh / docs/mail.md / deploy/mailu: address-domain vs server-host split,
  Mailu API token, MX for the address domain, local-relay SMTP for verify codes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(mailu): pin Docker network subnet to match SUBNET; ignore runtime state

The base compose declares no network, so Docker assigns the default bridge an
arbitrary subnet that won't match mailu.env SUBNET — breaking Mailu's internal
service auth/relay. Add a docker-compose.override.yml.example that pins the
default network to 192.168.203.0/24, and gitignore the live override + Mailu
runtime state (mailu.env, certs/, data/).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(mail): plaintext loopback IMAP so the gateway bypasses Mailu's front

Mailu's front (nginx mail proxy) pre-authenticates against Mailu's user DB before
proxying to Dovecot, which rejects the Dovecot master-user login <addr>*gateway.
The gateway must reach Dovecot directly. The imap container has no TLS cert (only
the front does), so the bypass is plaintext over loopback — the master password
never leaves the host.

- mailbox: IMAPConfig.Plaintext dials with DialInsecure (loopback only).
- main.go: mailClientFor sets Plaintext from AGENTBBS_MAIL_IMAP_PLAINTEXT.
- override.example: add the unbound resolver (admin needs DNSSEC), webmail image
  fix (2024.06 uses mailu/webmail), and publish Dovecot 143 on 127.0.0.1:14143.
- docs/mail.md: document the front-bypass, the dovecot.conf master passdb (Mailu
  includes that exact filename), and the 644 master-users perms (640 = temp_fail).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* deploy(mailu): wire gateway IMAP to the loopback Dovecot path in setup.sh

setup.sh §9e set AGENTBBS_MAIL_IMAP_ADDR to the front's :993, which the front's
auth proxy rejects for the master-user login (and would clobber the working
loopback wiring on every self-update). Point it at 127.0.0.1:14143 +
AGENTBBS_MAIL_IMAP_PLAINTEXT=1 instead, matching the override + docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(mail): give free members a webmail password at join@

The gateway opens mailboxes via the Dovecot master user (no member password),
but webmail (Roundcube) needs the member to have a password. join@ now sets a
fresh, readable webmail password via the Mailu API and shows it with the webmail
URL + login, so free members can use webmail at mail.profullstack.com.

- mailu: SetPassword (PATCH /user/<email> raw_password) + test.
- main.go: setWebmailPassword + readablePassword; join@ displays url/login/password.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 03:38:31 -07:00
c967da9f50 Add notify-creds subcommand to (re)email members git + mailbox creds
`agentbbs notify-creds` backfills credential emails to verified members
who signed up before the git/mailbox welcome emails existed.

- git (all verified): forgejo.EnsureUserReset resets each account to a
  fresh one-time password (must-change) and emails the web login link,
  username, and password. New method since the original one-time
  password is not recoverable for existing accounts.
- mailbox (all verified): ensures the forwardemail alias and emails the
  address + webmail link.
- Preview by default; --send executes. --git/--mail/--user filters.
  Refuses --send without SMTP; warns+skips when Forgejo/forwardemail
  are unconfigured.

Also folds in the welcome-email functions (gitWelcomeEmailBody,
mailWelcomeEmailBody, EnsureUser password return, provisionGit/
ensurePremium sends) that this builds on. README ops + forgejo tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:16:28 +00:00
6dc94bd784 feat(files): SFTP member storage — private workspaces + shared public area + mgmt TUI
Implements M4 (Files). A fully virtual Go SFTP server (pkg/sftp + crypto/ssh,
no OS users) wired as an "sftp" subsystem on the existing :22 wish listener, so
members reach their files with their login key:

    sftp files@bbs.profullstack.com     # scp/rsync ride the same endpoint

Identity is the SSH key (the username is conventional/ignored). Two areas per
session: a private, quota-limited /me workspace and a single shared public file
area /public (old-school BBS file area; world-read, members-only write by
default, operator-moderated). This reverses the old NG1 "no sharing" boundary in
favour of one sanctioned, inspectable sharing surface (PRD §9.3 amended).

internal/files:
- backend.go  service, layout, quota/usage, live-session registry, operator API
- fs.go       per-session virtual FS; resolve() is the single security
              chokepoint (area confinement + symlink-escape guard) + pkg/sftp
              request handlers
- server.go   subsystem handler: key auth -> member session -> request server,
              with byte metering and force-disconnect
- tui.go      in-BBS member browser (hub plugin "Files")
- admin.go    operator management TUI: sessions, workspaces/quotas, public area

Operator console: ssh sftp@<host> (allowlist-gated; sftpadmin@/filesadmin@
aliases) — list/disconnect sessions, set per-user quotas, revoke SFTP access,
toggle public write, moderate the public area.

store: files_access (per-user quota override + revoked) and files_settings
(public-write mode) tables + methods. main.go wiring guarded by AGENTBBS_FILES
(+ AGENTBBS_FILES_QUOTA_MB, default 1 GiB). Route names reserved.

Tests (incl -race): path traversal/confinement, symlink-escape rejection,
public-write ACL, quota enforcement, usage accounting, and an end-to-end run
against a real SFTP client. Docs: docs/files.md; PRD §5.3/§5.3.1/§9.3 + README
updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:35:04 +00:00
de5517c000
fix(pods): never downgrade AGENTBBS_POD_IMAGE on a transient build failure (#54)
The deploy's rootless-podman context intermittently fails (pre-pull/build/even
image-exists), which made setup.sh upsert AGENTBBS_POD_IMAGE back to the base
ubuntu and silently strip Claude Code/Codex from pods. Only upsert when we
actually have localhost/agentbbs-pod:latest; otherwise leave the configured
value untouched (the agentbbs daemon uses the local image from its own session).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:31:27 -07:00
f5d3740a4c
Merge pull request #53 from profullstack/feat/pod-agent-forward
feat(pods): SSH agent forwarding → git push from your pod with your key
2026-06-23 02:17:05 -07:00
94ce79374c feat(pods): SSH agent forwarding → git push from the pod with your key
Code in your pod and push to git.profullstack.com using the SAME SSH key you
signed in with — nothing is copied into the pod. When a member attaches with
agent forwarding (ssh -A), agentbbs listens on a fresh unix socket in a
per-user agent dir bind-mounted at /run/agentbbs-agent and proxies it back over
the session; the pod shell gets SSH_AUTH_SOCK pointed at it. The pod image's
ssh_config sends git@git.profullstack.com to Forgejo's SSH server (:2222), so
`git clone git@git.profullstack.com:you/repo.git` just works.

- pods.go: agentDir + startAgent (per-session socket, cleaned up on exit);
  Attach injects SSH_AUTH_SOCK when ssh.AgentRequested; ensure() bind-mounts the
  agent dir and self-heals idle pods missing it. No main.go change needed —
  charmbracelet/ssh sets AgentRequested from the session request loop.
- pods/Containerfile: /etc/ssh/ssh_config.d entry (port 2222, user git,
  accept-new) so the conventional git@ URL reaches Forgejo.
- setup.sh: keep using an already-built pod image if a later rebuild
  transient-fails, so a flaky deploy never downgrades pods to the base image.

Build/vet/test/gofmt clean. Image rebuilt on the host; `ssh -G
git.profullstack.com` resolves to port 2222 / user git. End-to-end push needs a
live `ssh -A` session (validate after deploy).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:16:29 +00:00
281d4b55fd
Merge pull request #52 from profullstack/feat/landing-page
feat(web): rich landing page — BBS history + all commands
2026-06-23 02:05:34 -07:00
2330d9dcb7
Merge pull request #51 from profullstack/feat/pod-ai-tools
feat(pods): Claude Code + Codex in member pods
2026-06-23 02:05:19 -07:00
3c3b1b1b06
Merge pull request #50 from profullstack/feat/agentgit-ssh-keys
feat(agentgit): SSH-key auth for git.profullstack.com + push
2026-06-23 02:04:49 -07:00
f6ce174cae feat(web): rich landing page — what a BBS is + every command
Replace the stub site root with a full page: a short "what's a BBS?" history
(1980s dial-up boards, SysOps, door games, FidoNet/Usenet) and the complete
command list — join/bbs/NAME/pod/mail/news/irc/game/domain over SSH — plus the
web services (AgentGit profiles, IRC, member homepages) and the git
"membership is your account" push flow. Regenerated on every run (templated
from $DOMAIN/$GIT_DOMAIN/$IRC_DOMAIN) so it stays current as features land.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:59:14 +00:00
c8edd2eed2 feat(pods): Claude Code + Codex in member pods (custom image)
Members can now code in their pod: build a custom pod image (FROM the base
Ubuntu) that ships git, openssh-client, Node.js 22, and the Claude Code
(`claude`) and Codex (`codex`) CLIs. BYO key — no credentials are baked in; a
member exports their own ANTHROPIC_API_KEY / OPENAI_API_KEY (or uses the tools'
login flow), stored in their persisted home.

- pods/Containerfile: the image (also drops a BYO-key + git-push login hint).
- setup.sh: build it on the host (rootless podman, layer-cached), switch
  AGENTBBS_POD_IMAGE to localhost/agentbbs-pod:latest (upserted for existing
  installs), keeping the base image if the build fails.
- pods.go: image-aware self-heal — an idle pod on an out-of-date image is
  recreated (home volume kept) so the new tooling rolls out without a manual
  rebuild and without disturbing active sessions.

Verified: image builds on the host; inside it node v22, git, ssh, `claude
--version` (2.1.186) and `codex --version` (0.142.0) all run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:48:38 +00:00
f210b4296b feat(agentgit): register members' SSH keys + enable git push over SSH
Make git.profullstack.com a real, key-authenticated git host for every BBS
member ("BBS membership is the git account", SSH-key auth end to end):

- forgejo.EnsureKey: register a member's SSH public key on their Forgejo
  account (idempotent, ignores the key comment). So the key they sign in to
  the BBS with is also their git push key.
- provisionGit now takes the session public key and registers it after
  ensuring the account; called on email verification AND (newly) on every
  member login, so members who predate AgentGit — or whose key wasn't
  registered yet — are backfilled automatically and off the hot path.
- setup.sh:
  - admin token scopes write:admin,read:user,write:user (the old write:admin
    alone failed userExists' /users lookup, so provisioning never worked).
  - REQUIRE_SIGNIN_VIEW=false so member profiles + public repos are viewable
    at git.profullstack.com/<name> (private repos stay private; accounts are
    still created only by agentbbs).
  - Enable Forgejo's built-in SSH server (port 2222, BUILTIN_SSH_SERVER_USER=git)
    and open the firewall, so members push to git@git.profullstack.com:2222.

Verified live: all members provisioned, git.profullstack.com/chovy serves the
profile, and a push over ssh://git@host:2222 with a registered key succeeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:39:37 +00:00
c5489a458e
Merge pull request #49 from profullstack/feat/arcade-hangman
feat(arcade): add Hangman built-in game + leaderboard
2026-06-22 07:59:57 -07:00
93eaef1184 feat(arcade): add Hangman built-in game + leaderboard
Hangman joins Snake as a built-in, leaderboard-backed TUI game (PRD §5.1).
Endless mode: each solved word banks points (longer words and unused guesses
score more) and deals a fresh word with full lives; the run ends when one word
exhausts all six wrong guesses, persisting the total for members under the
"hangman" score key. Guests play without persisting, same as Snake.

Generalize the leaderboard board to take a game name and split the single
"Leaderboard" row into per-game "Leaderboard — Snake" / "Leaderboard — Hangman"
entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:59:06 +00:00
threebeats
a56f3afbca
fix: add GameNames to IsReservedName checks (#42)
GameNames ("game", "games") are reserved SSH route usernames for
AgentGames but IsReservedName did not check GameNames, allowing
members to register accounts that collide with SSH routes.

Fixes #40

Co-authored-by: root <root@vultr.guest>
2026-06-22 05:23:50 -07:00
threebeats
94846b91f3
fix: block shared/reserved IP ranges in SSRF guard (#47)
Adds Carrier-Grade NAT (100.64.0.0/10), benchmarking (198.18.0.0/15),
and documentation/example (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24)
ranges to the isBlockedIP check.

Go's net.IsPrivate() covers RFC1918 but not these shared/reserved ranges.

Fixes #43

Co-authored-by: root <root@vultr.guest>
2026-06-22 05:23:15 -07:00
threebeats
5f9d66e1a7
fix: reject malformed NNTP OVER ranges instead of returning all articles (#48)
parseRange previously returned (0, MaxInt64) for unparseable input,
causing OVER/XOVER to deliver the full article overview instead of
returning an empty result. Now returns (0, 0) for any parse error.

Fixes #45

Co-authored-by: root <root@vultr.guest>
2026-06-22 05:22:58 -07:00
threebeats
c0378ece3c
fix: remove outdated ssh irc@ route from README (#39)
The in-BBS ssh irc@ route no longer exists — internal/auth only reserves
'irc' as a name but has no handler for it. docs/irc.md already documents
this correctly.

- Removed ssh -t irc@ command and 'built-in IRC client' language
- Updated to reflect that members connect with their own client
- Kept correct native TLS and WebSocket connection examples

Co-authored-by: threebeats <threebeats@users.noreply.github.com>
2026-06-19 02:30:06 -07:00
d65c2bcecb
Merge pull request #36 from profullstack/fix/arcade-ncurses-term
fix(arcade): give ncurses games a TERM so they launch
2026-06-18 05:42:41 -07:00
481e715d89 fix(arcade): give ncurses games a TERM so they launch
The 80s arcade classics (Space Invaders/nInvaders, Pac-Man/pacman4console,
Tetris/tint, Moon Patrol/moon-buggy) are ncurses programs: initscr() fails
with "Error opening terminal" when TERM is unset. Game subprocesses were
built with exec.Command and no Env, so they inherited the agentbbs systemd
daemon's environment — which has no TERM — and every game exited before
drawing a frame. DOOM was unaffected because doom-ascii writes ANSI directly
and never touches terminfo.

Thread the client PTY's TERM through plugin.Context and hand each sandboxed
game a curated environment (TERM, PATH, HOME, LANG=C.UTF-8) instead of the
daemon's. Curating the env also stops leaking operator secrets (e.g.
COINPAY_API_KEY) into third-party game binaries.

Verified live on bbs.profullstack.com: Space Invaders and Pac-Man now render;
previously all four died instantly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 12:01:26 +00:00
Руслан Латыпов
beba7dc388
Fix unused and ineffectual variables (#32) 2026-06-16 03:21:39 -07:00
threebeats
9c2886c9b8
fix: case-sensitive flag comparison in AgentMail bot (#35) 2026-06-16 03:20:17 -07:00
337011fa03
refactor(pods): drop redundant tuneApt apt-sandbox hack (#31)
The rootless-podman default capability set (already on main) lets apt drop
to the _apt user on its own, so disabling the apt download sandbox via
tuneApt is dead code. Removing it; apt now works the proper way.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 08:23:54 -07:00
e0a267e343
Fix/pod public html bind mount (#30)
* feat(pods/admin): root admin alias, default-caps cleanup, pod rebuild script

- auth: add `root` as an admin-console route alias (alongside admin/sysop);
  still gated by $AGENTBBS_ADMINS — the name confers nothing on its own.
- pods: drop the now-redundant tuneApt apt-sandbox hack. Rootless podman keeps
  its default capability set, so apt/chown/su work without disabling the
  download sandbox.
- scripts/rebuild-pods.sh: recreate all member pods (keeps home volumes) so
  they pick up the current container profile on next `ssh pod@`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(arcade,ui): 80s arcade classics + shared menu theme

Arcade games (PRD §5.1), generalizing the sandboxed-PTY DOOM path into an
external-game registry: Space Invaders (nInvaders), Pac-Man (pacman4console),
Tetris (tint/vitetris), Moon Patrol (moon-buggy). Binaries resolve from
assets/bin, PATH, then /usr/games, so a distro install or a hand-built binary
lights each game up; missing games are skipped with a discovery hint. Installed
on the host via `scripts/fetch-assets.sh --arcade` (apt), wired into setup.sh
behind FETCH_ARCADE (default on).

UI: new shared ui.Theme.MenuItem widget (accent cursor + badge, description
shown only for the focused row) adopted by the hub and arcade menus; the hub
groups rows under Features/Sessions headers and the arcade under
DOOM/ARCADE/BUILT-IN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 08:06:32 -07:00
362b47fdde
Feat/members messaging (#29)
* fix(deploy): build Go binaries on the runner, ship them, SKIP_BUILD on box

The deploy SSHed into the ~458MB droplet and ran `go build` there. The Go
linker's peak memory OOM-killed the build — and with it the sshd serving the
deploy session — surfacing as "Connection closed by remote host" (exit 255).
It was flaky because it tracked momentary memory pressure from the co-resident
ergo/forgejo/tor/podman/agentbbs processes (run #25 passed, #26 failed on
near-identical code).

Build both binaries on the 16GB GitHub runner instead (pure-Go, modernc
sqlite, so CGO_ENABLED=0 static cross-build), scp them to the droplet, and run
setup.sh with SKIP_BUILD=1 so the box never compiles. Arch is detected from
the droplet so amd64/arm64 both work. setup.sh now also skips the Go toolchain
download when SKIP_BUILD=1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(members): member directory + store-and-forward messaging

A members-only hub plugin (the BBS "who") plus user-to-user messaging:

- internal/store: messages table + SendMessage/Inbox/UnreadCount/MarkRead, and
  OnlineUsers (open sessions) for presence. MarkRead is recipient-scoped so a
  member can only clear their own mail.
- plugins/members: directory with online dots + last-seen, a finger-style
  profile view, a minimal compose box, and an inbox that marks read on open.
- ssh msg@host <user> [text]: scriptable CLI to leave a note (body from args or
  stdin), mirroring the existing finger route; "msg"/"message" are reserved.
- hub: "N unread" badge on login (hubMOTD). plugin.Context gains Host for member
  homepage URLs.

Extends the existing finger@ behavior (ssh <name>@host) rather than replacing it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 07:48:07 -07:00
d763d732a3
fix(deploy): build Go binaries on the runner, ship them, SKIP_BUILD on box (#27)
The deploy SSHed into the ~458MB droplet and ran `go build` there. The Go
linker's peak memory OOM-killed the build — and with it the sshd serving the
deploy session — surfacing as "Connection closed by remote host" (exit 255).
It was flaky because it tracked momentary memory pressure from the co-resident
ergo/forgejo/tor/podman/agentbbs processes (run #25 passed, #26 failed on
near-identical code).

Build both binaries on the 16GB GitHub runner instead (pure-Go, modernc
sqlite, so CGO_ENABLED=0 static cross-build), scp them to the droplet, and run
setup.sh with SKIP_BUILD=1 so the box never compiles. Arch is detected from
the droplet so amd64/arm64 both work. setup.sh now also skips the Go toolchain
download when SKIP_BUILD=1.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 07:09:57 -07:00
68899180a7
Merge pull request #26 from profullstack/fix/pod-public-html-selfheal
fix(pods): self-heal pre-existing pods to pick up the public_html bind
2026-06-15 06:50:22 -07:00
2d6fba5f16 fix(pods): self-heal pre-existing pods to pick up the public_html bind
The box auto-deploys (self-update timer pulls origin/main, rebuilds, restarts
agentbbs), but `systemctl restart agentbbs` only restarts the daemon — it never
touches the long-lived per-user pod containers. ensure() also short-circuits on
any container that already exists, so pods created before the homepage bind
landed would never gain the /home/dev/public_html mount without a manual
`podman rm`. That defeats the "everything happens automatically on push" goal.

Make ensure() self-healing: when a pod exists but lacks the public_html mount,
recreate it so the bind is applied. The named home volume survives `rm`, so the
member's files are kept. Only heal when the pod is idle (attached count 0) to
avoid pulling a running pod out from under an active session — an unbound pod
heals on its next idle attach. New hasMount() inspects the container's mounts.

Net effect: push to main -> self-update redeploys within the timer interval ->
the next `ssh pod@` recreates the pod with the bind. No manual step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 13:50:06 +00:00
cbe7a44bd5
Merge pull request #25 from profullstack/fix/pod-public-html-bind-mount
fix(pods): bind-mount host public_html into the pod so ~/public_html is served
2026-06-15 06:44:37 -07:00
06bc5631d8 fix(pods): bind-mount host public_html into the pod so ~/public_html is served
A member's pod home (/home/dev) is a named container volume, but Caddy serves
<name>.<host> from the host path <data>/users/<name>/public_html. The two were
disconnected, so editing ~/public_html/index.html in the pod never changed the
served page — contradicting the on-screen "edit ~/public_html to make it yours"
instruction.

Bind-mount <data>/users/<user>/public_html at /home/dev/public_html when pods
start. The host tree is created if absent so the mount source exists, and under
the docker fallback (uid 1000) the one-shot init container now also chowns the
bind path; rootless podman maps container root to the host service user that
already owns the tree, so no chown is needed there.

Detect now takes the host users dir; pass "" to disable the bind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 13:42:08 +00:00
lazyGPT07
c480e62f39
Fix NNTP IHAVE missing message ID (#24)
Co-authored-by: lazyGPT07 <lazyGPT07@users.noreply.github.com>
2026-06-15 01:34:13 -07:00
phucnguyen1707
36c6e0e275
Handle repeated NNTP command whitespace (#20) 2026-06-14 20:25:04 -07:00
phucnguyen1707
0944fa2cb4
Merge pull request #22 from phucnguyen1707/fix-over-single-article
Fix single article NNTP overview ranges
2026-06-14 20:24:50 -07:00
336ff00fa3
Merge pull request #15 from profullstack/feat/irc-store-auth
feat(irc): gate IRC on the BBS user store; remove ssh irc@; external clients only
2026-06-14 20:23:54 -07:00
7a90c50242
Merge pull request #18 from profullstack/fix/qryptinvite-flaky-tamper-test
Fix flaky TestTamperedTokenFails (qryptinvite) reddening main CI
2026-06-14 10:06:03 -07:00
169a73d180 Fix flaky TestTamperedTokenFails in qryptinvite
flipLastChar toggled only the last base64 char of the signature segment.
For a 64-byte Ed25519 signature that char carries unused trailing bits, so
the toggle could decode back to the same bytes and still verify — making the
"corrupted signature must fail" assertion flaky (it reddened main CI).

Corrupt a decoded signature byte (sig[0] ^= 0xFF) and re-encode instead, so
the signature always differs. Verified deterministic over 50 runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:05:30 +00:00
8113b6d60e
Merge pull request #17 from profullstack/feat/mailu-stack
Self-host Mailu mail stack at mail.profullstack.com
2026-06-14 09:56:34 -07:00
45c46db373
Merge pull request #16 from profullstack/feat/bbs-mail-reader
AgentMail: paid-member mailbox reader (TUI + bot mode) on the BBS
2026-06-14 09:56:28 -07:00
b304922451
Merge pull request #14 from profullstack/feat/bbs-logo-banner
Show the Profullstack </> ASCII logo on join@ and the hub
2026-06-14 09:56:22 -07:00
e165ecee26 Add AgentMail: paid-member mailbox reader (TUI + bot mode) on the BBS
internal/mailbox is a transport-agnostic mail client for Founding Lifetime
members: read, search, compose, send, reply, flag, delete.

- types/transport/client: paid-gated facade returning JSON-serializable
  structs (same shapes as @logicsrc/plugin-agentmail)
- memory.go: in-memory transport (tests/dev/reference) + tests
- imap.go/smtp.go: real backend — go-imap/v2 (Dovecot IMAP, master-user
  login) + net/smtp submission via the co-located relay; go-message parses
  bodies/attachments
- reader_tui.go: Bubble Tea reader for humans (list/read/flag/delete)
- bot.go: JSON in/out mode for agents (ssh mail@host list|read|send|…)
- wired as the paid "Mail" hub entry and the ssh mail@ route + auth.MailNames

Connects to the self-hosted Mailu stack at mail.profullstack.com /
smtp.profullstack.com. Rebased onto main; build + vet + tests clean (Go 1.26).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:49:04 +00:00
6b4db2b934 Use the Profullstack </> ASCII logo as the BBS banner
Replace the figlet "profullstack.com" wordmark with a scaled-down ASCII
rendition of the Profullstack </> mark (derived from the brand image),
shown on the join@ onboarding and the ssh <name>@ hub. Reuses the existing
banner plumbing; the mark is sourced from internal/brand and rendered in
Profullstack red.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:46:59 +00:00