Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ Just two SSH front doors: **`join@`** to onboard a new key, then
domains are all reached from there.

**Membership:** a verified-email account is **free** — you get a personal Docker
pod and a homepage at `https://bbs.profullstack.com/~name`. **Premium** ($10
one-time, lifetime) adds a personal `name@bbs.profullstack.com` email (via
forwardemail.net) and custom domains.
pod and a homepage at `https://bbs.profullstack.com/~name`. **Founding Lifetime
Member** ($99 one-time, first 1,000 accounts only) adds, for life: a personal
`name@bbs.profullstack.com` email + webmail (via forwardemail.net), custom
domains, and Tor access (`ssh tor@` — fetch URLs & join IRC over Tor).

No browser, no install, no client download. The BBS is a hub of hot-swappable
plugins around one shared account system; the full product plan is in
Expand Down Expand Up @@ -137,7 +138,7 @@ server. Full details: [`docs/irc.md`](docs/irc.md).
- **Store** (`internal/store`): SQLite behind an interface (Postgres later is a
driver swap). Users, sessions, scores, pod subscriptions.
- **Payments** (`internal/payments`): CoinPay REST API (coinpayportal.com) for
the $10 lifetime membership + HMAC payment references; manual grant for ops.
the $99 Founding Lifetime membership + HMAC payment references; manual grant for ops.

## License

Expand Down
24 changes: 15 additions & 9 deletions cmd/agentbbs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// ssh bbs@host the BBS hub, guests welcome (play@/guest@ are aliases)
// ssh <name>@host the hub as a member/agent (SSH key required)
// ssh join@host onboarding: registers your key, confirms your email with an
// emailed code, then offers $10 lifetime Premium (CoinPay)
// emailed code, then offers $99 Founding Lifetime (CoinPay)
// ssh pod@host your personal Linux pod — free for verified members
// ssh domain@host point your own domain at your homepage (Premium; add/rm/list)
// ssh admin@host the operator admin console ($AGENTBBS_ADMINS only)
Expand Down Expand Up @@ -372,7 +372,7 @@ func readLine(s ssh.Session, in *bufio.Reader) (string, error) {

// handleJoin runs onboarding interactively in one SSH session: register the
// visitor's key, confirm their email with a code we email them, then offer the
// $10 lifetime Premium membership (CoinPay). It then disconnects.
// $99 Founding Lifetime membership (CoinPay). It then disconnects.
func (a *app) handleJoin(s ssh.Session) {
fp := auth.Fingerprint(s.PublicKey())
if fp == "" {
Expand Down Expand Up @@ -432,7 +432,7 @@ func (a *app) handleJoin(s ssh.Session) {
" homepage https://" + a.host + "/~" + u.Name,
}, "\n"))

// 2) Premium ($10 lifetime): personal @host email + custom domains.
// 2) Founding Lifetime ($99 one-time): personal @host email + custom domains.
a.offerPremium(s, &u)
_ = s.Exit(0)
}
Expand Down Expand Up @@ -577,7 +577,7 @@ func (a *app) ensurePremium(u *store.User) bool {
func (a *app) showPremiumWelcome(s ssh.Session, u store.User) {
lines := []string{
"",
" ★ Premium — thanks! Your perks:",
" ★ Founding Lifetime Member — thanks! Your perks:",
"",
" email " + a.fe.Address(u.Name),
" forwards " + u.Email,
Expand All @@ -592,7 +592,7 @@ func (a *app) showPremiumWelcome(s ssh.Session, u store.User) {
wish.Println(s, strings.Join(lines, "\n"))
}

// offerPremium pitches the $10 lifetime membership — a personal @host email and
// offerPremium pitches the $99 Founding Lifetime membership — a personal @host email and
// custom domains. When CoinPay can mint a charge in-session it shows the exact
// amount and deposit address; otherwise it falls back to a pay command.
// Non-blocking: the member pays out of band and perks unlock on their next
Expand All @@ -607,9 +607,15 @@ func (a *app) offerPremium(s ssh.Session, u *store.User) {

lines := []string{
"",
" Upgrade to Premium — " + payments.PremiumPriceLabel + ", one-time:",
" • your own email " + a.fe.Address(u.Name) + " (forwards to you)",
" ★ Founding Lifetime Member — $" + payments.PremiumAmount() + ", one-time",
" Only the first " + payments.FoundingCap + " accounts. Pay once, keep it for life.",
"",
" Everything in your free membership stays free — founding adds these",
" bonus features, forever:",
" • your own email " + a.fe.Address(u.Name) + " (forwards to you, webmail included)",
" • custom domains point yourdomain.com at your homepage",
" • Tor access ssh tor@" + a.host + " — fetch URLs & join IRC over Tor",
" • locked-in price founding rate is yours for life — never renew, never pay again",
"",
}
if c, ok, err := payments.CreatePremiumCharge(ref); ok && err == nil {
Expand Down Expand Up @@ -739,7 +745,7 @@ func (a *app) handleDomain(s ssh.Session) {
_ = s.Exit(1)
return
}
// Custom domains are a Premium perk ($10 lifetime). ensurePremium also
// Custom domains are a Founding Lifetime perk ($99 one-time). ensurePremium also
// catches a payment that settled since their last visit.
if !a.ensurePremium(&u) {
wish.Println(s, strings.Join([]string{
Expand Down Expand Up @@ -883,7 +889,7 @@ func (a *app) torMember(s ssh.Session, route string) (store.User, bool) {
return store.User{}, false
}
if !a.ensurePremium(&u) {
wish.Println(s, " "+route+" is a Premium feature ($10 lifetime). Upgrade: ssh join@"+a.host)
wish.Println(s, " "+route+" is a Founding Lifetime Member feature ($99 one-time, lifetime). Upgrade: ssh join@"+a.host)
_ = s.Exit(1)
return store.User{}, false
}
Expand Down
12 changes: 8 additions & 4 deletions internal/payments/payments.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package payments gates the one-time $10 lifetime Premium membership on
// Package payments gates the one-time $99 Founding Lifetime membership on
// CoinPay (coinpayportal.com). It talks to the CoinPay REST API directly over
// HTTP — no `coinpay` CLI needs to be installed on the host.
//
Expand All @@ -7,7 +7,7 @@
// COINPAY_API_KEY Bearer key for the CoinPay API (cp_live_…)
// AGENTBBS_COINPAY_MERCHANT_ID merchant/business id payments are created under
// AGENTBBS_COINPAY_API_URL API base (default https://coinpayportal.com/api)
// AGENTBBS_PREMIUM_AMOUNT fiat amount (default 10)
// AGENTBBS_PREMIUM_AMOUNT fiat amount (default 99)
// AGENTBBS_PREMIUM_CURRENCY fiat currency (default USD)
// AGENTBBS_PREMIUM_BLOCKCHAIN settlement chain (default eth)
//
Expand Down Expand Up @@ -35,11 +35,15 @@ const PodTerm = 31 * 24 * time.Hour

// PremiumPriceLabel is the human-readable price for the one-time lifetime
// membership offered at join@.
const PremiumPriceLabel = "$10 (lifetime)"
const PremiumPriceLabel = "$99 (lifetime)"

// FoundingCap is the marketing cap on Founding Lifetime memberships — the offer
// is pitched as available to the first this-many accounts.
const FoundingCap = "1,000"

// Premium charge parameters — overridable via env so the offer can change
// without a rebuild.
func PremiumAmount() string { return envOr("AGENTBBS_PREMIUM_AMOUNT", "10") }
func PremiumAmount() string { return envOr("AGENTBBS_PREMIUM_AMOUNT", "99") }
func PremiumCurrency() string { return envOr("AGENTBBS_PREMIUM_CURRENCY", "USD") }
func PremiumBlockchain() string { return envOr("AGENTBBS_PREMIUM_BLOCKCHAIN", "eth") }

Expand Down
4 changes: 2 additions & 2 deletions internal/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ type Store interface {
// SetPremiumPayment records the CoinPay payment id of a pending premium
// charge so a later visit can verify whether it settled.
SetPremiumPayment(userID int64, payID string) error
// GrantPremium marks the account as a lifetime premium member (the $10
// one-time membership), recording the CoinPay payment reference. Idempotent.
// GrantPremium marks the account as a lifetime premium member (the $99
// Founding Lifetime membership), recording the CoinPay payment reference. Idempotent.
GrantPremium(userID int64, paymentRef string) error

RecordSession(userID int64, username, remote, route string) (int64, error)
Expand Down
Loading