commit f37b3a0b1ab658f686cf0cf531404127b847a36b equwal <truex@equwal.com> 2026-09-22 16:05:57 -0700 Sync is free, and the home page can link to a pre-order of teams Sync on sbm.subread.space is free. The README no longer gives a price for it. SBM_TEAMS_URL is the address of a page that pre-sells team bookmarks, for example a Stripe Payment Link. When it is set, the home page tells about teams and links to that page. The server refuses to start when the value is not an http or https address.
README.md | 5 +++-- main.go | 9 ++++++++- pages.html | 4 ++++ server_test.go | 11 +++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md index 91ae916..c608472 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,7 @@ It is one small Go program. It keeps its data in plain files: no database. The only dependency is `golang.org/x/crypto` for bcrypt. The clients use **https://sbm.subread.space** unless you set another -server. That server is free for 30 days, then $3 a month or $30 a year. -Run your own server free of charge. +server. Sync on that server is free. You can also run your own server. ## How sync works @@ -72,6 +71,8 @@ Settings, all through the environment: SBM_ADDR address to listen on (default 127.0.0.1:8750) SBM_DATA data directory (default ./data) SBM_CONTACT email address on the privacy page (optional) + SBM_TEAMS_URL page that pre-sells team bookmarks (optional): the + home page links to it SBM_TRIAL_DAYS days of sync before payment, with billing on (default 30) SBM_BILLING_START date when billing starts, as 2026-10-01: accounts from before it get the full trial from that date diff --git a/main.go b/main.go index 05f47b5..f84950a 100644 --- a/main.go +++ b/main.go @@ -41,6 +41,7 @@ type server struct { trial time.Duration since time.Time // start of billing: no trial ends before since + trial contact string + teams string // address of the page that pre-sells team bookmarks, or "" prices map[string]string // plan ("month", "year"): Stripe price ID labels map[string]string // plan: price as text limit limiter @@ -65,6 +66,10 @@ func main() { log.Fatalf("SBM_BILLING_START: %v", err) } } + teams := os.Getenv("SBM_TEAMS_URL") + if t, err := url.Parse(teams); teams != "" && (err != nil || t.Host == "" || (t.Scheme != "https" && t.Scheme != "http")) { + log.Fatalf("SBM_TEAMS_URL: not an address: %q", teams) + } store, err := openStore(env("SBM_DATA", "data")) if err != nil { log.Fatal(err) @@ -77,6 +82,7 @@ func main() { trial: time.Duration(days) * 24 * time.Hour, since: since, contact: os.Getenv("SBM_CONTACT"), + teams: teams, prices: map[string]string{"month": os.Getenv("STRIPE_PRICE_MONTH"), "year": os.Getenv("STRIPE_PRICE_YEAR")}, labels: map[string]string{"month": "monthly", "year": "yearly"}, } @@ -165,6 +171,7 @@ func (s *server) routes() http.Handler { type page struct { Title, Error, Email, State, URL, Contact string + Teams string // address of the pre-order page Month, Year string TrialDays int Billing, CanSubscribe, Unconfirmed bool @@ -172,7 +179,7 @@ type page struct { } func (s *server) page(title string) page { - return page{Title: title, URL: s.site, Contact: s.contact, Billing: s.stripe != nil, + return page{Title: title, URL: s.site, Contact: s.contact, Teams: s.teams, Billing: s.stripe != nil, Month: s.labels["month"], Year: s.labels["year"], TrialDays: int(s.trial.Hours() / 24)} } diff --git a/pages.html b/pages.html index efe47ce..2e9b62f 100644 --- a/pages.html +++ b/pages.html @@ -46,6 +46,10 @@ your computers, the sbm app for Android, and the sbm add-on for Firefox and Chrome.</p> {{if .Billing}}<p>Free for {{.TrialDays}} days. Then {{.Year}} or {{.Month}}. Cancel at any time.</p>{{end}} <p>{{if .Email}}<a href="/account">Your account</a>{{else}}<a href="/signup">Create an account</a> or <a href="/login">sign in</a>.{{end}}</p> +{{with .Teams}}<h2>Teams</h2> +<p>Coming: bookmarks for teams. The members of a team share one bookmark +file, and sync it on each of their devices. <a href="{{.}}">Pre-order the +founding plan</a>.</p>{{end}} <p class="note">sbm Sync is free software under the AGPL-3.0. You can <a href="https://github.com/equwal/sbm-sync">run your own server</a>.</p> {{template "foot" .}}{{end}} diff --git a/server_test.go b/server_test.go index 197a714..fc04d93 100644 --- a/server_test.go +++ b/server_test.go @@ -498,6 +498,17 @@ func TestOldVersionsGo(t *testing.T) { } } +func TestTeamsPreorderLink(t *testing.T) { + e := newEnv(t, false) + if strings.Contains(e.body(http.DefaultClient, "/"), "Pre-order") { + t.Error("the home page offers teams without SBM_TEAMS_URL") + } + e.s.teams = "https://buy.stripe.com/test_x" + if !strings.Contains(e.body(http.DefaultClient, "/"), `<a href="https://buy.stripe.com/test_x">Pre-order`) { + t.Error("the home page has no pre-order link") + } +} + func TestEmailCheck(t *testing.T) { e := newEnv(t, false) e.withMail()