Skip to content

Commit c6d098f

Browse files
authored
Merge pull request #2 from profullstack/feat/membership-and-autonomous-deploy
bbs.profullstack.com host + join@/<name>@ README + CoinPay HTTP
2 parents 7a85f2d + eb53835 commit c6d098f

8 files changed

Lines changed: 340 additions & 169 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,18 @@ jobs:
5151
# Deploy whichever branch was pushed (main or master), so a rename
5252
# "just works". For workflow_dispatch this is the chosen branch.
5353
DEPLOY_BRANCH: ${{ github.ref_name }}
54+
# App secrets injected into the droplet's env file by setup.sh.
55+
# GitHub masks these in logs; setup.sh upserts them idempotently.
56+
COINPAY_API_KEY: ${{ secrets.COINPAY_API_KEY }}
57+
COINPAY_MERCHANT_ID: ${{ secrets.COINPAY_MERCHANT_ID }}
5458
run: |
5559
ssh -i ~/.ssh/id_deploy -p "$DEPLOY_PORT" \
5660
-o BatchMode=yes -o StrictHostKeyChecking=yes \
5761
"${DEPLOY_USER}@${DEPLOY_HOST}" \
58-
"sudo -n env BRANCH=$(printf %q "$DEPLOY_BRANCH") bash -s" <<'REMOTE'
62+
"sudo -n env BRANCH=$(printf %q "$DEPLOY_BRANCH") \
63+
COINPAY_API_KEY=$(printf %q "$COINPAY_API_KEY") \
64+
COINPAY_MERCHANT_ID=$(printf %q "$COINPAY_MERCHANT_ID") \
65+
bash -s" <<'REMOTE'
5966
set -euo pipefail
6067
REPO=https://github.com/profullstack/agentbbs.git
6168
BRANCH="${BRANCH:-main}"
@@ -67,7 +74,10 @@ jobs:
6774
fi
6875
git -C "$SRC" fetch --depth 1 origin "$BRANCH"
6976
git -C "$SRC" reset --hard "origin/$BRANCH"
70-
exec env BRANCH="$BRANCH" "$SRC/setup.sh"
77+
exec env BRANCH="$BRANCH" \
78+
COINPAY_API_KEY="${COINPAY_API_KEY:-}" \
79+
COINPAY_MERCHANT_ID="${COINPAY_MERCHANT_ID:-}" \
80+
"$SRC/setup.sh"
7181
REMOTE
7282
7383
- name: Smoke-test that agentbbs serves :22

README.md

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@
44
by Profullstack, Inc.
55

66
```bash
7-
ssh bbs@profullstack.com # the hub: arcade (DOOM, snake), leaderboards — guests welcome
8-
ssh join@profullstack.com # register + confirm email by code, then the Premium offer
9-
ssh <name>@profullstack.com # the hub as a member — or finger someone else's name
10-
ssh pod@profullstack.com # your own Linux pod — FREE for verified members
11-
ssh domain@profullstack.com # point your domain at your homepage (Premium)
12-
ssh video-<code>@profullstack.com # join a PairUX video call as truecolor ASCII
13-
ssh agent@profullstack.com # chat with the operator's AI agent
7+
ssh join@bbs.profullstack.com # new here? register + confirm your email, get your account
8+
ssh <name>@bbs.profullstack.com # your BBS: hub, arcade, your pod, chat, domains — all inside
149
```
1510

16-
**Membership:** verified-email members are **free** — each gets a Docker pod
17-
(`ssh pod@`) and a homepage at `https://host/~name`. **Premium** ($10 one-time,
18-
lifetime) adds a personal `name@host` email (via forwardemail.net) and custom
19-
domains.
11+
Just two SSH front doors: **`join@`** to onboard a new key, then
12+
**`<name>@`** for everything else — the hub, your pod, the arcade, chat, and
13+
domains are all reached from there.
14+
15+
**Membership:** a verified-email account is **free** — you get a personal Docker
16+
pod and a homepage at `https://bbs.profullstack.com/~name`. **Premium** ($10
17+
one-time, lifetime) adds a personal `name@bbs.profullstack.com` email (via
18+
forwardemail.net) and custom domains.
2019

2120
No browser, no install, no client download. The BBS is a hub of hot-swappable
2221
plugins around one shared account system; the full product plan is in
@@ -29,7 +28,7 @@ plugins around one shared account system; the full product plan is in
2928
|---|---|
3029
| M0 — core hub (wish server, auth, plugin contract, SQLite) ||
3130
| M1 — arcade (doom-ascii + Freedoom, sandbox, saves, leaderboards) ||
32-
| Pods (`pod@`, rootless containers, CoinPay membership) ||
31+
| Pods (rootless containers, free for verified members) ||
3332
| Video (`video-<code>@`, PairUX/LiveKit → ASCII streaming) ||
3433
| `agent@` chat (configurable agent backend) + finger ||
3534
| M2 — admin console ||
@@ -43,7 +42,7 @@ plugins around one shared account system; the full product plan is in
4342
go build -o agentbbs ./cmd/agentbbs
4443
scripts/fetch-assets.sh # build doom-ascii + fetch Freedoom (optional)
4544
./agentbbs # listens on :2222
46-
ssh -p 2222 bbs@localhost
45+
ssh -p 2222 join@localhost # onboard, then: ssh -p 2222 <name>@localhost
4746
```
4847

4948
Configuration (env):
@@ -53,13 +52,14 @@ Configuration (env):
5352
| `AGENTBBS_ADDR` | `:2222` | listen address |
5453
| `AGENTBBS_DATA` | `./data` | SQLite db, host key, per-user dirs |
5554
| `AGENTBBS_ASSETS` | `./assets` | doom binary + wads |
56-
| `AGENTBBS_HOST` | `profullstack.com` | hostname shown in messages |
55+
| `AGENTBBS_HOST` | `bbs.profullstack.com` | hostname shown in messages |
5756
| `AGENTBBS_SANDBOX` | `auto` | `bwrap` / `prlimit` / `none` |
5857
| `AGENTBBS_POD_IMAGE` | `debian:stable-slim` | pod base image |
5958
| `AGENTBBS_POD_MEM` / `AGENTBBS_POD_CPUS` | `512m` / `1` | pod caps |
6059
| `AGENTBBS_POD_KEEP` | unset | `1` keeps pods running after disconnect |
61-
| `AGENTBBS_COINPAY_PAY_TMPL` | coinpay default | pay command shown to users |
62-
| `AGENTBBS_COINPAY_VERIFY_CMD` | unset | verifier; exit 0 = paid |
60+
| `COINPAY_API_KEY` | unset | CoinPay API key (Premium payments) |
61+
| `AGENTBBS_COINPAY_MERCHANT_ID` | unset | CoinPay merchant/business id |
62+
| `AGENTBBS_FORWARDEMAIL_API_KEY` | unset | forwardemail.net key (Premium email) |
6363

6464
Ops:
6565

@@ -86,15 +86,16 @@ Full details, required secrets, and ops commands: [`docs/deploy.md`](docs/deploy
8686
- **Plugins** (`internal/plugin`): `ID/Title/Description/RequiresAuth/New`; a
8787
plugin owns the session until it emits `ExitMsg`. Adding a feature is one
8888
interface implementation plus one registration.
89-
- **Routing**: SSH username selects the surface — hub, onboarding, or pod.
89+
- **Routing**: SSH username selects the surface — onboarding (`join@`) or your
90+
hub (`<name>@`); pods/arcade/chat/domains are features inside the hub.
9091
- **Pods** (`internal/pods`): rootless Podman preferred, hardened Docker
9192
fallback; per-user volume; cpu/mem/pids caps; no host root, ever.
9293
- **Sandbox** (`internal/sandbox`): bubblewrap (ro rootfs, no net, private
9394
scratch) or prlimit for arcade binaries.
9495
- **Store** (`internal/store`): SQLite behind an interface (Postgres later is a
9596
driver swap). Users, sessions, scores, pod subscriptions.
96-
- **Payments** (`internal/payments`): CoinPay CLI integration + HMAC payment
97-
references; manual grant path for ops.
97+
- **Payments** (`internal/payments`): CoinPay REST API (coinpayportal.com) for
98+
the $10 lifetime membership + HMAC payment references; manual grant for ops.
9899

99100
## License
100101

cmd/agentbbs/main.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func main() {
9898
return
9999
}
100100

101-
host := env("AGENTBBS_HOST", "profullstack.com")
101+
host := env("AGENTBBS_HOST", "bbs.profullstack.com")
102102
fe := forwardemail.ConfigFromEnv()
103103
if fe.Domain == "" {
104104
fe.Domain = host // personal addresses live on the BBS host by default
@@ -397,11 +397,14 @@ func (a *app) ensurePremium(u *store.User) bool {
397397
if u.Premium {
398398
return true
399399
}
400-
ref := payments.PremiumReference(u.PubKeyFP)
401-
if paid, checked := payments.VerifyPremium(ref); !checked || !paid {
400+
// Verify the CoinPay payment we created for them (if any) has settled.
401+
if u.PremiumPayID == "" {
402+
return false
403+
}
404+
if paid, checked := payments.VerifyPremium(u.PremiumPayID); !checked || !paid {
402405
return false
403406
}
404-
if err := a.st.GrantPremium(u.ID, ref); err != nil {
407+
if err := a.st.GrantPremium(u.ID, u.PremiumPayID); err != nil {
405408
log.Error("grant premium", "err", err)
406409
return false
407410
}
@@ -456,6 +459,10 @@ func (a *app) offerPremium(s ssh.Session, u *store.User) {
456459
"",
457460
}
458461
if c, ok, err := payments.CreatePremiumCharge(ref); ok && err == nil {
462+
// Remember the payment id so a later connect can confirm settlement.
463+
if err := a.st.SetPremiumPayment(u.ID, c.ID); err != nil {
464+
log.Error("store premium payment id", "err", err)
465+
}
459466
amount := "$" + payments.PremiumAmount() + " " + payments.PremiumCurrency()
460467
if c.CryptoAmount != "" {
461468
cur := c.Currency
@@ -475,7 +482,7 @@ func (a *app) offerPremium(s ssh.Session, u *store.User) {
475482
if err != nil {
476483
log.Error("create premium charge", "err", err)
477484
}
478-
lines = append(lines, " pay: "+payments.PremiumPayCommand(ref))
485+
lines = append(lines, " Payment is temporarily unavailable — please try again shortly.")
479486
}
480487
lines = append(lines,
481488
"",

0 commit comments

Comments
 (0)