Files
custo-viewer/README.md
T
travandClaude Opus 5 996e45379a Fork ssb-viewer as custo-viewer
Rebrand package.json (name, homepage, repository) so render.js's page footer,
which is built from pkg.homepage + git HEAD, points at this repo rather than
upstream's ssb:// URL.

Track package-lock.json instead of ignoring it. It was gitignored upstream, but
this is a deployed application on a 469MB box running node 18 — a reproducible
dependency tree is the difference between RESTORE.md working and not.

Upstream's README is kept verbatim as UPSTREAM-README.md; the new one documents
the custo data model, including the two things that will otherwise cost someone
an afternoon: custodisco is the string "true", and no message ever sets
content.channel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192zBTNZKZn5svyJ5HTnYds
2026-08-22 19:30:42 -04:00

2.4 KiB

custo-viewer

The web view behind www.cust.ooo — a fork of ssb-viewer that renders custo items from a Scuttlebutt pub.

Upstream's own docs are kept verbatim in UPSTREAM-README.md.

What custo adds

/items One merged grid of every item the pub knows about, across all kiosks. New kiosks appear by being followed — no code change.
known-blob gate serveBlob only serves blobs referenced by a feed we replicate, so the node never hands out a stranger's cached blob.
exit on dead sbot bin.js exits when its muxrpc handle dies, instead of holding the port open and hanging every request forever.
blob-wanter.js Standing blobs.want orders for our own feeds' blobs, replacing what ssb-blobs sympathy used to do before it was turned off.

The data model

An item is an ordinary type: "post" carrying custo's own fields:

{ "type": "post", "custodisco": "true", "nft": "mint",
  "text": "![photo.jpg](&…sha256)\n\n…\n\n a #custodisco item ",
  "mentions": [{ "name": "photo.jpg", "type": "image/jpeg", "link": "&…sha256" }] }

A transfer of custody is a reply to that message:

{ "type": "post", "custodisco": "true", "nft": "give",
  "target": "@…ed25519", "root": "%…sha256", "branch": "%…sha256" }

Two things to know before writing a query against this:

  • custodisco is the string "true", not a boolean.
  • No message ever sets content.channel. The #custodisco hashtag exists only in post text. Anything keyed on the channel index will silently return nothing — which is why /channel/custodisco renders an empty page.

Messages are published by the kiosks (/home/trav/custodisco-kiosk/ssb-post.sh), not by this viewer. The viewer is read-only.

Running it

bin.js connects to a local ssb-server and serves on conf.viewer.port (8807). See UPSTREAM-README.md for the plugin-vs-standalone options.

Deployment

Deployed by rsync from a laptop, not by git pull — the server holds no push credentials, and one deploy path is better than two. The script, the systemd units, the nginx config, and the disaster-recovery runbook all live in the ops repo alongside this one (documents/custo/ssb-viewer), which is the source of truth for the server itself.

Never commit secrets here. Keys and server config belong in the ops repo.

AGPL-3.0+, inherited from upstream.