Recently Written · git

sbm-sync

Sync server for sbm bookmark files: one small Go program, plain files, AGPL

git clone https://github.com/equwal/sbm-sync

Log | Files | Refs


commit 6b049a0a5b6a3319325369fce0e30b7fa3da26f7
equwal <truex@equwal.com>
2026-09-25 02:15:58 -0700

Add the feed timer, a deploy script and the feed docs

contrib/sbm-feed.service and contrib/sbm-feed.timer run `sbm-sync feed`
every 30 minutes as the sbm user with the environment of the server.
contrib/deploy.sh builds the server for Linux, keeps the old binary as a
backup, installs the binary, the units and sfeed, and restarts the
service. The README describes the feed page, the API and the data.

 .gitignore               |  1 +
 README.md                | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 contrib/deploy.sh        | 36 ++++++++++++++++++++++++++++++++++++
 contrib/sbm-feed.service | 24 ++++++++++++++++++++++++
 contrib/sbm-feed.timer   | 11 +++++++++++
 5 files changed, 119 insertions(+)
diff --git a/.gitignore b/.gitignore
index 84c3a8c..30a0cce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 data/
 /sbm-sync
 /sbm-sync.exe
+/sbm-sync-linux
 *.env
diff --git a/README.md b/README.md
index 9b9c855..4e6aae1 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,39 @@ 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.
+- 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.
+- Digest: turn it on, and the server sends you the new items by email once
+  a day, in the form of `sfeed_plain`. The first day after you turn it on
+  sends nothing, so that you do not get the whole history.
+
+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.
@@ -92,6 +125,11 @@ Plain HTTP, for any client with curl.
                            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
@@ -136,6 +174,12 @@ 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
@@ -210,6 +254,9 @@ The Chrome Web Store asks for them. To change them, edit the template
                                   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.
 
diff --git a/contrib/deploy.sh b/contrib/deploy.sh
new file mode 100644
index 0000000..527676a
--- /dev/null
+++ b/contrib/deploy.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# deploy.sh: build sbm-sync for Linux and install it on a server that runs
+# sbm-sync.service (see sbm-sync.service) with systemd and nginx.
+#
+#     sh contrib/deploy.sh root@example.org
+#
+# The script keeps the old binary as /usr/local/bin/sbm-sync.bak-<time>,
+# installs sfeed for the feed job, and starts sbm-feed.timer.
+set -e
+host=${1:?usage: sh contrib/deploy.sh user@host}
+cd "$(dirname "$0")/.."
+GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -o sbm-sync-linux .
+scp sbm-sync-linux contrib/sbm-feed.service contrib/sbm-feed.timer "$host:/tmp/"
+rm sbm-sync-linux
+ssh "$host" sh -s << 'EOF'
+set -e
+if ! command -v sfeed_update >/dev/null; then
+    DEBIAN_FRONTEND=noninteractive apt-get install -y -q sfeed
+fi
+if [ -f /usr/local/bin/sbm-sync ]; then
+    cp -p /usr/local/bin/sbm-sync "/usr/local/bin/sbm-sync.bak-$(date +%Y%m%d-%H%M%S)"
+fi
+install -m 755 /tmp/sbm-sync-linux /usr/local/bin/sbm-sync
+install -m 644 /tmp/sbm-feed.service /tmp/sbm-feed.timer /etc/systemd/system/
+rm /tmp/sbm-sync-linux /tmp/sbm-feed.service /tmp/sbm-feed.timer
+systemctl daemon-reload
+systemctl restart sbm-sync
+systemctl enable --now sbm-feed.timer
+sleep 1
+if ! systemctl is-active --quiet sbm-sync; then
+    journalctl -u sbm-sync -n 20 --no-pager
+    exit 1
+fi
+echo "sbm-sync: $(systemctl is-active sbm-sync), sbm-feed.timer: $(systemctl is-active sbm-feed.timer)"
+systemctl list-timers sbm-feed.timer --no-pager
+EOF
diff --git a/contrib/sbm-feed.service b/contrib/sbm-feed.service
new file mode 100644
index 0000000..1b3993f
--- /dev/null
+++ b/contrib/sbm-feed.service
@@ -0,0 +1,24 @@
+# The feed job of sbm-sync: it fetches the feeds of every account with
+# sfeed_update, looks through bookmarks for feeds, and sends the daily
+# digests. sbm-feed.timer starts it every 30 minutes. Install:
+#   apt-get install sfeed
+#   install -m 644 contrib/sbm-feed.service contrib/sbm-feed.timer /etc/systemd/system/
+#   systemctl daemon-reload && systemctl enable --now sbm-feed.timer
+[Unit]
+Description=sbm Sync feed job
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+User=sbm
+Group=sbm
+EnvironmentFile=/etc/sbm-sync.env
+Environment=SBM_DATA=/var/lib/sbm-sync
+ExecStart=/usr/local/bin/sbm-sync feed
+NoNewPrivileges=yes
+ProtectSystem=strict
+ProtectHome=yes
+PrivateTmp=yes
+PrivateDevices=yes
+ReadWritePaths=/var/lib/sbm-sync
diff --git a/contrib/sbm-feed.timer b/contrib/sbm-feed.timer
new file mode 100644
index 0000000..ab3cff5
--- /dev/null
+++ b/contrib/sbm-feed.timer
@@ -0,0 +1,11 @@
+# Runs sbm-feed.service every 30 minutes. See sbm-feed.service.
+[Unit]
+Description=Run the sbm Sync feed job every 30 minutes
+
+[Timer]
+OnCalendar=*:0/30
+RandomizedDelaySec=60
+Persistent=true
+
+[Install]
+WantedBy=timers.target