commit d7065626834246c9aa63396c09dbd956f8cbcd4d Spenser Truex <truex@equwal.com> 2026-10-01 23:21:18 -0700 Human README, slop code
README.md | 297 +++++--------------------------------------------------------- 1 file changed, 24 insertions(+), 273 deletions(-)
diff --git a/README.md b/README.md index 18509a1..7fba5b0 100644 --- a/README.md +++ b/README.md @@ -1,286 +1,37 @@ -# sbm-sync +# SBM Sync: A fully self-hostable cloud-synced simple tagged bookmarks multiplatform software -sbm-sync keeps your [sbm](https://github.com/equwal/sbm) bookmark file the -same on each of your devices: bm on your computers, the sbm app for Android -and the sbm add-on for Firefox and Chrome. +OFFICIAL WEBSITE: [sbmsync.com](https://sbmsync.com) - +For years I just used a simple dmenu shell script to do this, but now that we have the AI apocalypse, I put Fable 5.1 MAX at the task of making this for an hour. If you prefer the simple core program, just use my [sbm-suckless](https://github.com/equwal/sbm-suckless/) instead. AI was used there to fix some bugs and port the broken POSIX shell script to the usable C99 language spec. -The demo (80 seconds, also as [MP4](demo/sbm-demo.mp4)): bm adds a bookmark -in a terminal. The add-on in the browser shows it, and adds the page that it -shows. After `bm-sync`, bm has that bookmark too. All through -sbmsync.com. +(the following is the blog post at [recentlywritten.com/sbm-sync.html](https://recentlywritten.com/sbm-sync.html) -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. +# sbm Sync -The clients use **https://sbmsync.com** unless you set another server. -Sync on that server is free. You can also run your own server. The old -address, sbm.subread.space, still works for the clients that use it. +Suckless Bookmark Manager (now sbm Sync) -## How sync works +sbm is a bookmark manager, originally a dmenu script. It is now a multiplatform, cloud synced bookmark manager with support for teams. -A device sends its whole bookmark file and the name of the version that it -got last time. The server merges the changes of the device into its own -copy, keeps the result and sends it back. The device writes the result to -its file and keeps the new version name. +## Promo video (ai slop) +[](https://www.youtube.com/watch?v=iKmYWTCkW7c) -The merge compares whole lines: +## Try it on the web +* [sbmsync.com](https://sbmsync.com) -- A line that the device removed since its last version goes. -- A line that the device added comes in, at the end of the file, where bm - adds bookmarks. When the server has another line for the same bookmark - (the same URL, as bm compares URLs), the line of the device takes its - place. So an edit on a device wins. -- Lines that other devices added stay. +## Projects +* [sbm](https://github.com/equwal/sbm) multiplatform computer application +* [sbm-android](https://github.com/equwal/sbm-android) android app +* [sbm-extension](https://github.com/equwal/sbm-extension) firefox and chrome extensions +* [sbm-sync](https://github.com/equwal/sbm-sync) the sync server +* [sbm-suckless](https://github.com/equwal/sbm-suckless) the original POSIX shell script, suckless edition (no vibecode) -When the server does not know the last version of a device (for its first -sync), nothing is removed: the result is the union of both files. +## How it works +Fuzzy search bookmarks and quickly add/copy/view bookmarks. Now with syncing through the browser and everything. -## Bookmarks on the web +## Why +This tool gives you the power to spam links to your friends to the point that it annoys them. -After you sign in, the page **/bookmarks** shows your bookmarks in any -browser, also on a phone. The home page then has the search box too. The -page does what bm does: +Like many others, I find search engines to be pretty tedious to use. Searching my own bookmarks is much better. With sbm you can be like me and have BASED links at your fingertips. -- Search as fzf does, with the search of the add-on. The results change - while you type, and Enter opens the first result (Ctrl+Enter: in a new - tab), as in the add-on. Pick a tag. Sort by date, URL, description or - tag. Text that no bookmark matches opens as an address, or as a web - search. -- Search from the address bar of the browser: type sbm, a space and your - words. Pages link to an OpenSearch description, `/opensearch.xml`, and - the bookmarks page tells the address to add: - `https://sbmsync.com/bookmarks?q=%s`. -- Add a bookmark. A URL that is a bookmark already is refused. -- Edit or delete a bookmark. -- Edit the whole file, as `bm -e` does. -- Import the HTML file that a browser exports, as - `bm-import bookmarks.html | bm --merge` does: folders become tags, and - known URLs are skipped. A file of bookmark lines works too. -- Download the file. - -Each change goes through the same merge as a sync, so the changes that your -devices made since the page opened stay. The only script is `search.js`, -for the search while you type: it gets the results from the server. Without -it, the search form works too. - -The pages obey the rules of sync: the email address must be confirmed, and -with billing on, the account needs its trial or a subscription. The -download always works, because the bookmarks belong to their owner. - -## Feeds - -After you sign in, the page **/feed** follows RSS and Atom feeds for you. -It uses [sfeed](https://codemadness.org/sfeed.html) on the server: - -- Follow a feed: type its address and a name. The list is one plain file, - `feeds.txt`, with one feed on each line: the URL, a tab and the name. - A new account starts with the default feeds of the server - (`SBM_DEFAULT_FEEDS`; on sbmsync.com, the feed of recentlywritten.com). - You can unfollow them like any other feed. -- The server fetches the feeds every 30 minutes with `sfeed_update` and - shows the items newest first, with the feed, the time and the author. A - setting opens the links in a new tab. -- Explore: turn it on, and the server looks through your bookmarked pages - for feeds with `sfeed_web`, 20 pages with each update, and offers what it - finds. -- Share: each feed has a share link, and the list has one for all feeds - (`/feed/follow?url=...&name=...`). An account that opens the link gets a - button that follows the feeds. A visitor is asked to sign in or to create - an account, and comes back to the link after that. -- Digest: the server sends you the new items by email once a day, in the - form of `sfeed_plain`, unless you turn that off in the settings of the - page. The first day sends nothing, so that you do not get the whole - history. A feed that you follow later is the same: the digest sends only - the items that come after its first fetch. A server without an SMTP - server sends no digest. - -Read the feed in a terminal with the token of a device (`bm-sync login` -keeps one in `~/.config/sbm/sync`): - - curl -H "Authorization: Bearer $token" https://sbmsync.com/api/feed | sfeed_plain - curl -H "Authorization: Bearer $token" https://sbmsync.com/api/feeds - -`/api/feed` gives the items of all your feeds in the sfeed format, newest -first, so `sfeed_plain`, `sfeed_curses`, `sfeed_html` and the other sfeed -tools read it. `/api/feeds` gives `feeds.txt`. - -The feed job fetches only feeds and pages on the public internet: an -address that points to a private network, or to the server itself, is -skipped. The job runs as the command `sbm-sync feed`; `contrib/` has a -systemd service and a timer for it, and `contrib/deploy.sh` installs them -together with the server. - -## Protocol - -Plain HTTP, for any client with curl. - - POST /api/login form fields email, password - 200: a token, as text - POST /api/sync?base=V Authorization: Bearer <token> - body: the bookmark file - 200: the merged file; header Sbm-Version: <new V> - POST /api/logout Authorization: Bearer <token> - GET /api/account Authorization: Bearer <token> - 200: JSON: email; state, one sentence; plans, - the subscriptions that the account can start, - each with id and label; portal, true when - /api/portal works; terms, the address of the - terms of sale; the payment links support, - manage, teams and teams_large, when the server - has them - POST /api/checkout Authorization: Bearer <token>; form field plan - (an id from plans) - 200: the address of a Stripe Checkout page - POST /api/portal Authorization: Bearer <token> - 200: the address of the Stripe customer portal - GET /api/feed Authorization: Bearer <token> - 200: the items of all feeds, newest first, in - the sfeed(5) format, one item on each line - GET /api/feeds Authorization: Bearer <token> - 200: the feeds, one on each line: URL, tab, name - -Errors come as text: 401 (sign in again), 402 (the subscription or the -trial has ended), 403 (confirm the email address first), 404 (the server -has no billing), 409 (a subscription exists already, or no billing exists -yet), 413 (the file is larger than 4 MB), 429 (too many sign-in attempts). - -The add-on opens the Stripe pages in a new tab. After them, Stripe shows -the page `/done` of the server, which tells the user to go back to the -add-on. - - curl -d email=me@example.org --data-urlencode password=... https://sbmsync.com/api/login - curl -H "Authorization: Bearer $token" --data-binary @bookmarks \ - "https://sbmsync.com/api/sync?base=$version" - -## Run your own server - -This repository has all the source code of the server, under the -AGPL-3.0. You need a machine with a domain name that points to it. - -### With Docker - -Ports 80 and 443 must be open. Caddy gets the TLS certificate by itself. - - git clone https://github.com/equwal/sbm-sync - cd sbm-sync - cp .env.example .env # then set SBM_DOMAIN in .env - docker compose up -d - -The data is in the Docker volume `sbm-data`. To back it up: - - docker compose cp sbm-sync:/data ./backup - -To update, run `git pull`, then `docker compose up -d --build`. - -### Without Docker - - go build - SBM_URL=https://sbm.example.org ./sbm-sync - -Then put it behind a web server that does TLS. `contrib/` has a systemd unit, -an nginx site and a Caddyfile. With Go installed on another computer: - - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build - -For the feeds, install `sfeed` (a Debian package) and run `sbm-sync feed` -every 30 minutes with the same environment as the server: -`contrib/sbm-feed.service` and `contrib/sbm-feed.timer` do that with -systemd. `sh contrib/deploy.sh root@sbm.example.org` builds the server, -installs it, the timer and sfeed on the server, and restarts the service. - -### Connect the devices - -Sign in with the address of your server: `bm-sync login -https://sbm.example.org` on a computer. In the add-on and the app, type the -address in the field "Sync server". - -### Settings - -All settings come from the environment. With Docker, put them in `.env`. - - SBM_URL public address of the server (default http://localhost:8750) - 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 for up to 10 - people (optional): the home page links to it - SBM_TEAMS_LARGE_URL the same for teams of 11 people or more - SBM_SUPPORT_URL page of a supporter subscription (optional): the home - page and the account page link to it - SBM_MANAGE_URL page where supporters manage or cancel their - subscription, such as the login link of the Stripe - customer portal (optional, with SBM_SUPPORT_URL) - 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 - SBM_SMTP_HOST SMTP server that sends email (optional) - SBM_SMTP_PORT its port (default 587, with STARTTLS; 465 uses TLS - from the start) - SBM_SMTP_USER user name on the SMTP server - SBM_SMTP_PASSWORD password on the SMTP server - SBM_MAIL_FROM sender, as "sbm Sync <sync@example.org>" (default - SBM_SMTP_USER) - SBM_DEFAULT_FEEDS feed URLs, with spaces between them, that a new - account follows until it unfollows them (optional) - -Without `SBM_SMTP_HOST`, the server sends no email, and accounts need no -email check. With it, each new account gets an email with a link. The -account syncs only after someone opens that link. Accounts from a time -without `SBM_SMTP_HOST` need no check. - -To reset a password, the operator deletes the account in `accounts.json` -(with the server stopped), and the user signs up again. The bookmark files -stay on the devices. - -## Billing - -Billing is off unless `STRIPE_SECRET_KEY` is set. Without billing, sync -is free for all accounts. To turn it on: - -1. In Stripe, make a product with two recurring prices, for example $3 a - month and $30 a year. -2. Add a webhook endpoint `https://<your server>/stripe` for the events - `checkout.session.completed`, `customer.subscription.created`, - `customer.subscription.updated` and `customer.subscription.deleted`. -3. Turn on the customer portal in the Stripe settings, so that customers - can cancel and change their card. -4. Set these variables, then restart the server: - - STRIPE_SECRET_KEY sk_test_... or sk_live_... - STRIPE_WEBHOOK_SECRET whsec_... of the endpoint - STRIPE_PRICE_MONTH price_... of the monthly price - STRIPE_PRICE_YEAR price_... of the yearly price - -Keep the keys out of git: put them in the environment file of the service. - -The page `/terms` shows the terms of sale, for all payments of the server. -The Chrome Web Store asks for them. To change them, edit the template -`terms` in `pages.html`. - -## Data - - data/accounts.json accounts: email, bcrypt hash, token hashes, - hash of the email check code, Stripe - customer and subscription state - data/files/<account>/<sha256> the last 50 versions of each bookmark file - data/files/<account>/HEAD name of the current version - data/feeds/<account>/ the feeds: feeds.txt, the sfeedrc made - from it, items/<feed> from sfeed_update, - explore, and the state of the digest - -To back up, copy the directory. - -## Test - - go vet ./... && go test ./... - -The merge has property tests (with [rapid](https://github.com/flyingmutant/rapid)). - -## License - -AGPL-3.0. If you run a changed version for other people, give them its -source code. - -If sbm is useful to you, you can support it on [Ko-fi](https://ko-fi.com/truex). +## Support +Support the work at [ko-fi.com/truex](https://ko-fi.com/truex).