Commit graph

28 commits

Author SHA1 Message Date
f44f0c9ae8 join@: fix onboarding freeze — read raw PTY input instead of ReadString('\n')
ssh join@host requests a PTY by default, so the client's terminal goes into
raw mode: it sends keystrokes as they're typed (Enter is '\r', not '\n') and
does no local echo. The onboarding prompts (username, email, code) read with
bufio.ReadString('\n'), which never sees a '\n' and blocks forever — the user
sees a dead prompt at "Email:". The earlier "require a PTY" change made this
guaranteed rather than intermittent.

Add a readLine helper that reads byte-by-byte, treats '\r' or '\n' as the
line terminator, handles backspace, and echoes printable bytes back so the
user can see what they type. Route the three onboarding prompts through it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:30:52 +00:00
ac4b0873d9 join@: let new members pick their own username
Onboarding hard-coded the account name to member-<fp8>, so everyone got an
unmemorable handle like member-zafztqdk for ssh <name>@host and /~<name>.

New keys are now prompted for a username during join@. auth.SanitizeUsername
folds input to the hub/subdomain charset (lowercase [a-z0-9-], 3–20 chars,
'_'/space -> '-', no doubled/edge dashes); auth.IsReservedName blocks route and
infra labels (bbs/join/pod/domain/admin/agent/video/video-*/www/...). The name
must be free (UserByName) or we re-prompt; pressing enter keeps the member-<fp8>
default. Returning keys keep the name they already chose.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 10:43:09 +00:00
31c00cd65a setup.sh: persistent Go cache + HOME so timer deploys build instead of OOM/erroring
The self-update timer runs setup.sh as a systemd oneshot with no $HOME, so
`go build` aborted with "module cache not found: neither GOMODCACHE nor GOPATH
is set" before compiling — auto-deploys silently pulled new code but never
rebuilt or restarted. On top of that, a cold cache made the 458MB droplet
OOM-kill the compiler.

Set explicit, root-owned, persistent Go caches (/var/cache/agentbbs/{go,go-build})
plus HOME and GOMAXPROCS=1 in setup.sh, and add HOME=/root to the generated
agentbbs-update.service. A warm persistent cache means incremental redeploys
recompile almost nothing, keeping peak memory within RAM+swap; combined with
the existing `go build -p=1` the compiler no longer gets OOM-killed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 10:31:29 +00:00
cbc9069964
M3: AgentGames — agent-vs-agent games, ELO ladder, replays (#7)
A Gym-style game engine (PRD §5.2) with two transports sharing one
matchmaker, so an SSH agent and a WebSocket agent can be paired together.

Engine (internal/games):
  - Game/State contract (immutable positions); registry/catalog.
  - Phase-1 games: Tic-Tac-Toe (ttt) and Connect 4 (c4).
  - ELO (K=32, start 1500), a generic win/block/random GreedyBot.
  - Transport-agnostic NDJSON protocol + match driver: hello → state →
    move → result. We run no agent code — illegal move / per-move timeout /
    disconnect all forfeit (strict validation in place of a sandbox).
  - Matchmaker: per-game queue, bounded queue-wait; never abandons a match
    that started racing the wait timeout.

Transports:
  - SSH route game@ (ssh game@host ttt | join message), registered key,
    no PTY.
  - WebSocket /play (wss), bearer API token (agentbbs mint-token <user>);
    loopback behind Caddy.

Store: game_ratings (ELO ladder) + game_matches (full move log for replay)
+ api_tokens; Rating/SaveMatch satisfy games.Store; TopRatings/RecentMatches/
MatchByID/MintAPIToken/UserByToken. Banned accounts blocked.

Hub: plugins/agentgames — browse ladders, watch move-by-move replays, and
practice vs the bot (off the rated ladder).

Tests: engine (win/draw/legality), ELO, bot, full match via matchmaker with
replay, transport (deadline/closed), store round-trips. Verified live over
SSH (agent-vs-agent), WebSocket↔SSH cross-transport, forfeit-on-illegal-move,
and the hub ladder/replay views. Docs in docs/agentgames.md (the canonical
protocol spec, to mirror to logicsrc.com); README M3 → done.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 03:30:12 -07:00
41a8ff240d README: add hosting requirements (RAM, tiny-droplet SKIP_BUILD, Ubuntu 24.04); fix pod image default
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 10:18:46 +00:00
c272ed8a05 join@: require a PTY so onboarding can't hang
handleJoin reads the email and verification code interactively, but the
router deliberately skipped the active-PTY guard for join@ on the wrong
assumption that it "prints and disconnects." A client without a controlling
tty (ssh delegating prompts to ssh-askpass) gets no PTY, so the email prompt
blocked forever after the account banner.

Guard handleJoin for a PTY and emit a "reconnect with ssh -t" hint instead of
hanging; fix the misleading router comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 10:00:44 +00:00
sh1pt-actions-fleet[bot]
84e70668e8
sh1pt: install Node pnpm Test (node-pnpm-test@1.1.0) (#5)
* Add .github/workflows/test.yml via sh1pt node-pnpm-test@1.1.0

* Convert test workflow to Go (go test ./...)

agentbbs is a Go project (go.mod, go 1.26); the Node/pnpm workflow could
never pass here — pnpm/action-setup found no packageManager. Replace with
go test ./... and trigger on main (was master-only). Build/vet live in ci.yml.

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

---------

Co-authored-by: sh1pt-actions-fleet[bot] <287014002+sh1pt-actions-fleet[bot]@users.noreply.github.com>
Co-authored-by: Anthony Ettinger <anthony@profullstack.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 02:49:50 -07:00
sh1pt-actions-fleet[bot]
e75f751252
sh1pt: install Node pnpm CI (node-pnpm-ci@1.1.0) (#6)
* Add .github/workflows/ci.yml via sh1pt node-pnpm-ci@1.1.0

* Convert CI workflow to Go (build + vet)

agentbbs is a Go project (go.mod, go 1.26); the Node/pnpm workflow could
never pass here — pnpm/action-setup found no packageManager. Replace with
go build + go vet. Companion test.yml runs go test.

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

---------

Co-authored-by: sh1pt-actions-fleet[bot] <287014002+sh1pt-actions-fleet[bot]@users.noreply.github.com>
Co-authored-by: Anthony Ettinger <anthony@profullstack.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 02:49:34 -07:00
sh1pt-actions-fleet[bot]
d98ee1cce7
Add .github/workflows/vu1nz-scan.yml via sh1pt vu1nz-scan@1.0.0 (#4)
Co-authored-by: sh1pt-actions-fleet[bot] <287014002+sh1pt-actions-fleet[bot]@users.noreply.github.com>
2026-06-14 02:44:18 -07:00
232b8151a2
M2: admin console over ssh admin@ (users, sessions, moderation, plugins) (#3)
A privileged operator console reached as `ssh admin@host`, gated by route
plus the $AGENTBBS_ADMINS allowlist (admin status is operator-granted only,
never self-assigned in-band). It is a self-contained Bubble Tea model, not a
hub plugin, so it never appears in the public menu.

Sections (PRD §6):
  - Users & members: list accounts; b = suspend/ban (operators protected).
    Banned accounts are blocked at the hub and pod@ routes.
  - Sessions & pods: live in-memory session registry; k = disconnect.
  - Moderation & audit: admin action log + agent@ transcripts (tab to switch).
  - Config & plugins: runtime snapshot; space = enable/disable a plugin
    (persisted; filtered from the hub on next sign-in).

Every privileged action is written to a new admin_actions audit table.

store: + banned column, admin_actions and plugin_state tables, and the
backing methods (ListUsers/SetBanned/RecentSessions/LogAdminAction/
RecentAdminActions/RecentChatsAll/DisabledPlugins/SetPluginDisabled), with
unit tests. auth: admin allowlist helpers + tests. Docs in docs/admin.md;
README M2 flipped to done.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 02:40:18 -07:00
ceaf055e0e setup.sh: restart agentbbs on (re)deploy so rebuilt binaries actually load
enable --now leaves an already-running service untouched, so a redeploy's new
binary never loaded. Use enable + restart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:28:31 +00:00
31c444829d setup.sh: fix wildcard Caddy block — use host label placeholder (host_regexp isn't a built-in matcher)
caddy validate failed: 'module not registered: http.matchers.host_regexp'.
Drop the regexp; map the subdomain via {http.request.host.labels.N} where N is
the DOMAIN label count. The on-demand ask endpoint still gates cert issuance to
registered members.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:23:03 +00:00
ae8b1c25f4 Free per-user homepages at <name>.<host> (wildcard subdomains)
- sites.AskHandler now approves <name>.AGENTBBS_HOST on-demand-TLS when <name>
  is a registered member (so only real users mint subdomain certs).
- Caddyfile: *.${DOMAIN} block serving users/<name>/public_html with on_demand
  TLS; unknown users 404. Needs a wildcard DNS record *.${DOMAIN} -> host.
- Test: TestAskUserSubdomain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:20:34 +00:00
74c292f341 setup.sh: move socket-activated sshd (Ubuntu 24.04) to the admin port
On Ubuntu 22.10+/24.04 sshd is socket-activated via ssh.socket, which owns the
listen port and ignores sshd_config's Port — so the admin-port move silently
failed (safety check aborted before freeing :22). Override ssh.socket's
ListenStream to ADMIN_SSH_PORT when present; fall back to restarting the service
on classic setups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:15:37 +00:00
2600b1e3b6 setup.sh: support tiny droplets — SKIP_BUILD (prebuilt binaries), auto-swap, -p=1, unzip
512MB droplets OOM-kill the Go build (livekit/redis/modernc deps). Add:
- ensure_swap: adds a 3G swapfile on low-RAM hosts (build + runtime headroom)
- SKIP_BUILD=1: use prebuilt /usr/local/bin/{agentbbs,ascii-live} (copied from
  CI or a dev box) instead of compiling on the host
- go build -p=1 to cap peak memory when building on-box
- install unzip (Freedoom fetch needs it)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:08:30 +00:00
c6d098f7de
Merge pull request #2 from profullstack/feat/membership-and-autonomous-deploy
bbs.profullstack.com host + join@/<name>@ README + CoinPay HTTP
2026-06-14 01:42:47 -07:00
eb538355d0 Merge remote-tracking branch 'origin/main' into feat/membership-and-autonomous-deploy 2026-06-14 08:42:14 +00:00
06693bcf6c README: collapse to join@ + <name>@ model; drop stale CoinPay-CLI/$1-pod docs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 08:37:19 +00:00
7fd10c3436 payments: call CoinPay REST API directly (drop coinpay CLI dependency)
CreatePremiumCharge/VerifyPremium now POST /payments/create and GET
/payments/:id against the CoinPay API (Bearer COINPAY_API_KEY, business_id =
AGENTBBS_COINPAY_MERCHANT_ID), so the droplet needs no coinpay CLI installed.
The created payment id is stored (store: User.PremiumPayID + premium_pay_id col
+ SetPremiumPayment); ensurePremium verifies that id on a later connect and
grants premium on confirmed/forwarded status. Removed the CLI command-template
env knobs; added AGENTBBS_COINPAY_API_URL. Tests: httptest-backed payments_test
+ store SetPremiumPayment test. Build/vet/gofmt/test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 08:27:33 +00:00
5c0feda8d4 Wire CoinPay merchant id + deploy secret injection
- payments: AGENTBBS_COINPAY_MERCHANT_ID -> --business-id on the premium
  create/pay commands (coinpay CLI reads COINPAY_API_KEY from env for auth).
- deploy.yml: forward COINPAY_API_KEY + COINPAY_MERCHANT_ID (GitHub secrets,
  masked in logs) to the remote setup.sh.
- setup.sh: idempotent upsert_env writes those secrets into agentbbs.env
  (COINPAY_API_KEY, AGENTBBS_COINPAY_MERCHANT_ID, COINPAY_BUSINESS_ID),
  preserving the rest. No secret values are committed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 08:19:48 +00:00
3dd6fe863c Use bbs.profullstack.com host throughout README + default
The SSH examples and AGENTBBS_HOST default showed the bare apex
profullstack.com; the BBS lives on bbs.profullstack.com. Fix the README
examples/membership/config-table and align the code default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 08:01:39 +00:00
7a85f2dbbb
Autonomous deploy + free-pod/Premium-email membership (#1)
Deploy automation (idempotent, runs on every deploy):
- .github/workflows/deploy.yml: push to main/master (or dispatch) SSHes to the
  droplet and re-runs setup.sh; deploys the pushed branch; smoke-tests :22.
- scripts/self-update.sh + agentbbs-update.timer: autonomous backstop that
  redeploys only when origin advances.
- setup.sh hardened: flock, fetch+reset (survives force-push), fixed the
  always-skipped arcade asset fetch path.

Membership model:
- Free, email-verified members get their own Docker pod (pod@ paywall removed)
  and a /~name homepage (seeded at join@).
- join@ is now interactive: email -> emailed 6-digit code -> enter code.
- Premium ($10 one-time, lifetime via CoinPay) grants a personal
  <name>@host email (new internal/forwardemail; forwardemail.net aliases) and
  custom domains (domain@ gated to Premium).
- ensurePremium() silently verifies/grants/provisions on hub login, join@, and
  domain@. New-signup details emailed to AGENTBBS_SIGNUP_NOTIFY (subject "bbs").

Store: User.Premium + premium/premium_ref cols, ConfirmEmailCode, GrantPremium.
Tests: store_premium_test.go, forwardemail_test.go. Build/vet/gofmt/test green.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 00:59:52 -07:00
3230807421 Autonomous deploy + free-pod/Premium-email membership
Deploy automation (idempotent, runs on every deploy):
- .github/workflows/deploy.yml: push to main/master (or dispatch) SSHes to the
  droplet and re-runs setup.sh; deploys the pushed branch; smoke-tests :22.
- scripts/self-update.sh + agentbbs-update.timer: autonomous backstop that
  redeploys only when origin advances.
- setup.sh hardened: flock, fetch+reset (survives force-push), fixed the
  always-skipped arcade asset fetch path.

Membership model:
- Free, email-verified members get their own Docker pod (pod@ paywall removed)
  and a /~name homepage (seeded at join@).
- join@ is now interactive: email -> emailed 6-digit code -> enter code.
- Premium ($10 one-time, lifetime via CoinPay) grants a personal
  <name>@host email (new internal/forwardemail; forwardemail.net aliases) and
  custom domains (domain@ gated to Premium).
- ensurePremium() silently verifies/grants/provisions on hub login, join@, and
  domain@. New-signup details emailed to AGENTBBS_SIGNUP_NOTIFY (subject "bbs").

Store: User.Premium + premium/premium_ref cols, ConfirmEmailCode, GrantPremium.
Tests: store_premium_test.go, forwardemail_test.go. Build/vet/gofmt/test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 07:58:51 +00:00
Anthony Ettinger
1086d57a4d auth: gofmt User struct alignment
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 15:55:40 +00:00
85024afcf6 setup.sh: wire join@ email verification through Caddy
Proxy /verify on the main site to the agentbbs loopback endpoint and
surface AGENTBBS_HTTP_ADDR + the SMTP knobs in the generated env file
(links are logged when SMTP is unconfigured; existing env files keep
working via the in-binary 127.0.0.1:8088 default).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:54:27 +00:00
f3f8e70996 bbs.profullstack.com launch kit: provisioner, custom domains, email verify, ascii-live
- setup.sh: idempotent one-shot droplet provisioner — agentbbs on :22
  (admin OpenSSH moved to :2202), rootless podman, Caddy front end for
  https://bbs.profullstack.com with tilde-style /~user homepages
- internal/sites + domain@ SSH route: self-service custom domains
  (ssh domain@host add example.com) backed by a symlink farm and an
  on-demand-TLS ask endpoint so Caddy only issues certs for mapped hosts
- internal/mail + join@ email verification: optional email at signup,
  confirmation link served by a loopback /verify endpoint behind Caddy
- internal/source + cmd/ascii-live: live video → terminal ASCII groundwork
  (docs/ascii-live.md)
- store: additive sqlite migrations (email/verify columns, domains table)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:53:19 +00:00
9b0f465946 Video calls (PairUX→ASCII), agent@ chat, and finger routes
ssh video-<code>@ joins a PairUX/LiveKit call rendered as truecolor
ASCII (▀ half-blocks, 2 pixels per cell); video@ prompts for a code.
Codes are minted by PairUX only. Pipeline: VP8 RTP → PLI keyframe
requests → ivfwriter remux → ffmpeg decode/scale → RGB24 → ANSI →
bubbletea over the SSH PTY. Subscriber-only, no audio in v1.

ssh agent@ opens a persisted chat with the operator's agent —
AGENTBBS_AGENT_CMD runs per message (stdin→stdout), e.g. `claude -p`.

ssh <member>@ with someone else's name prints a classic finger card
(.plan, member since, last seen) and disconnects; your own name still
enters the hub.

cmd/lkpublish: dev publisher for testing (explicit -fps pacing; lksdk
IVF replay mispaces from file timebase alone, measured 1fps from a
15fps file; dimensions required or dynacast pauses the track).

Verified end-to-end against livekit-server --dev: 128k truecolor cells
/ 20k distinct colors streamed over a real SSH session; chat round-trip
and finger card verified over SSH. Go toolchain pinned to 1.26 via
mise.toml (lksdk requirement).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 11:43:38 +00:00
f3b085a08f AgentBBS: M0 core hub, M1 arcade, pods with CoinPay membership
A modern BBS over SSH for humans and AI agents (docs/PRD.md), plus the
pods addendum (docs/pods.md). Go + charmbracelet (wish/bubbletea).

SSH routes by username:
- bbs@/play@   hub as guest
- <name>@      hub as member/agent (key required; one key = one account)
- join@        onboarding: registers the key, prints instructions
               (incl. coinpay pay command with HMAC payment ref), kicks
- pod@         personal Linux container, paid membership $1/mo via
               CoinPay; rootless podman preferred, hardened docker
               fallback (cap-drop ALL, no-new-privileges, uid 1000,
               cpu/mem/pids caps, per-user volume)

M0: plugin contract (ID/Title/Description/RequiresAuth/New + ExitMsg),
hub menu, SQLite store (users/sessions/scores/pod_subscriptions),
session audit, grant-pod ops command.

M1 arcade: doom-ascii + Freedoom via scripts/fetch-assets.sh, sandbox
runner (bwrap/prlimit), PTY-bridged exec with orphan reaping, snake
with global leaderboard, member save dirs + private ~/wads scan.

Verified over real SSH: join/paywall/grant/pod attach + write
persistence across reconnects, guest+member hubs, DOOM launch, no
orphaned processes after hard disconnect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 11:08:17 +00:00