on master help sign in

A public web page listing the apps your Urbit ship publishes, so anyone can install them.

git clone https://git.iko.soy/shelf.git
Shelf logo

%shelf

A public web page that lists the apps your Urbit ship publishes, so anyone can install them on their own ship.

The page shows your ship's %treaty alliance: your own published desks plus any desks you republish from other ships. Each app has an Install button that opens it in the visitor's own Landscape, and the Dojo |install command as a fallback. The page follows the visitor's light or dark preference. It has no JavaScript except for the Copy buttons. The same URL also returns the list as JSON.

%shelf answers on exactly one path, /shelf. Run it behind a reverse proxy that serves it at the root of a dedicated domain (see Serving it on its own domain). The page's links and its install form all point at /.

Requirements

Install

Install %shelf from ~donmec-sallud, which publishes it:

~sampel-palnet:dojo> |install ~donmec-sallud %shelf

%shelf then gets its updates from ~donmec-sallud. shelf.iko.soy is %shelf running on that ship. It lists all the apps ~donmec-sallud offers, %shelf among them.

To install from source instead, build the desk:

nix build github:iko-soy/shelf

result is now the desk folder (see Building with Nix). Without Nix, use desk/ from a checkout in its place. Then, with your ship running:

~sampel-palnet:dojo> |new-desk %shelf
~sampel-palnet:dojo> |mount %shelf
cp -R result/* /path/to/pier/shelf/
chmod -R u+w /path/to/pier/shelf    # files from the Nix store are read-only
~sampel-palnet:dojo> |commit %shelf
~sampel-palnet:dojo> |install our %shelf

Either way, once %shelf runs, the page is at https://<your-ship>/shelf, and Landscape shows a Shelf tile that opens it. It is meant to be opened through your own domain, though: on the ship's address the page renders, but its links lead to /, which belongs to Landscape.

Building with Nix

flake.nix has one output: the desk, packages.<system>.desk, which is also the default package. Building it copies desk/ into the Nix store; nothing is compiled, because Clay compiles the Hoon on your ship when you commit the desk.

CommandBuilds
nix build github:iko-soy/shelfthe latest commit on the default branch
nix build github:iko-soy/shelf/<commit>a specific commit
nix buildyour checkout, from the repo root

Each one leaves a result symlink to the desk in the store:

result/
├── desk.bill  desk.docket-0  sys.kelvin
├── app/  gen/  lib/  mar/  sur/  tests/
└── web/

Update

A %shelf installed from ~donmec-sallud gets its updates from that ship.

To update a source install, build and copy the desk into the mount again, as above, and run |commit %shelf. Then check that .^(cass:clay %cw /=shelf=) moved: if the new version doesn't build, Clay keeps the old one and logs nothing.

A source install comes from your own ship, so it gets no over-the-air updates. After a kernel upgrade, Clay holds it until desk/sys.kelvin lists the new %zuse version; update that file (or pull a release that has) and commit. +vats %shelf shows whether it is running or held.

Choose what's listed

%shelf lists your %treaty alliance. A desk needs a desk.docket-0 file before you can publish it.

%shelf has a docket of its own, so :treaty|publish %shelf lists it on its own page, where others can install it. The docket carries the app icon inline, as a URL-encoded data: URL, so the icon shows in Landscape and on other ships' pages with nothing to host.

A republished app appears once your ship has received its docket from the publisher, which happens a few seconds after you ally them. Apps are sorted by title. %shelf reads the alliance on every request, but browsers may show a copy up to a minute old.

Serving it on its own domain

Point a domain such as apps.example.com at a Caddy server that sends every request to the ship's /shelf:

apps.example.com {
	@other not path /
	redir @other /
	rewrite * /shelf
	reverse_proxy 127.0.0.1:8080 {
		header_up Forwarded "proto={scheme}"
	}
}

This config was tested with Caddy 2.11 in front of a fake ship.

Analytics and other tags

You can add raw HTML to the end of the page's <head> and <body>, for example an analytics snippet:

:shelf|head '<script defer data-domain="apps.example.com" src="https://plausible.io/js/script.js"></script>'
:shelf|body '<script async src="https://example.com/counter.js"></script>'

JSON

Ask for JSON with an Accept header, at the same URL:

curl -H 'Accept: application/json' https://apps.example.com/
[{
  "ship": "~sampel-palnet", "desk": "harbor", "title": "Harbor",
  "info": "Every desk on your ship: its version, its source, its updates.",
  "color": "#24466f", "image": "https://…/harbor.svg", "version": "2.0.1",
  "website": "https://…", "license": "MIT", "hash": "0v…",
  "install": "|install ~sampel-palnet %harbor"
}]

How installing works for a visitor

  1. Install opens a panel under the app. It asks for the visitor's ship URL (for example sampel-palnet.tlon.network) and shows the Dojo command.
  2. The form is POSTed. %shelf checks the URL and keeps only its scheme, host and port. It remembers that origin in a cookie for a year, then redirects to <ship>/apps/landscape/search/direct/apps/~publisher/desk. That is Landscape's own "Get App" dialog. The visitor's ship fetches the app's details directly from the publisher, and a visitor who isn't logged in lands there after their ship's login page.
  3. On later visits, Install links straight there. The button reads Install on ~sampel-palnet when the URL's first label is a planet name (or a moon or comet), or any ship name on arvo.network, tlon.network or startram.io. Otherwise it reads Install on your ship: galaxy and star names double as ordinary words, like dev. or master.. A Change ship link in the header clears the cookie.

Guards:

Traffic

Every request that arrives without a cookie makes Eyre start a guest session. Crawlers, uptime monitors, curl and other sites fetching the JSON all do this. Each session costs your ship about 0.2 KB of memory for 7 days, plus about 0.3 KB of event log. A page view takes the ship about 50 ms with 10 apps, so requests start to queue above about 20 a second.

Uninstalling

Gall gives an agent no chance to clean up, so Eyre keeps the /shelf binding after %shelf stops. Until you remove it, /shelf answers 503 at once.

  1. Remove the site from your reverse proxy first. Otherwise visitors get that 503 page, or, once the binding is gone, an endless redirect loop between Landscape's login page and /.

  2. :docket|uninstall %shelf, or Uninstall from the tile's menu in Landscape. This removes the tile and suspends the agent; a plain |uninstall %shelf would leave a suspended tile behind. Your head/body HTML is kept, and |install our %shelf brings the page straight back. Uninstalling a %shelf installed from ~donmec-sallud also stops its updates. To turn them back on, run |install ~donmec-sallud %shelf after |install our %shelf.

  3. To delete that state too: |nuke %shelf, =desk & (answer y).

  4. Optionally, free the path. Only the binding's owner can remove it, so take it over from the dojo, then remove it:

    |pass [%e %connect [~ /shelf] %docket]
    |pass [%e %disconnect [~ /shelf]]

    Installing %shelf again binds /shelf again. %shelf re-binds every time it loads, so it also restores the binding if someone removed it by hand.

Changes from the design handoffs

%shelf follows two design handoffs: the page itself (layout, copy, behaviour, JSON, a skeleton agent) and a later icon update. Rendered with the mock's sample data, the page matches the design mock pixel for pixel in light and dark mode, apart from the icons and names. Everything below differs from the handoffs, and why.

Serving

Installing

Reading the alliance

Page

Icons and docket (the icon update)

Other

Development

The desk is self-contained. desk/ holds everything Clay needs:

PathWhat
desk.docket-0What Landscape shows: title, description, color, icon, version, website, license. Its image is web/shelf-icon.svg URL-encoded; a test checks they match
app/shelf.hoonThe agent: Eyre binding, routing, scries, responses
lib/shelf.hoonPure helpers and the page (Sail)
sur/shelf.hoon, mar/shelf/action.hoon, gen/shelf/The head/body settings
web/shelf.css, web/copy.jsInlined into the page at build time with /*
web/shelf-icon.svg, web/favicon.svgThe app icon (also inlined in the docket) and the favicon, served at ?asset=
tests/lib/shelf.hoonUnit tests, including the rendered page
lib/sigil.hoon, lib/sigil/symbols.hoonVendored sigil renderer (MIT, ~paldev; shapes from @urbit/sigil-js, MIT, Tlon)
other lib/, mar/, sur/ filesCopied unmodified from %base (urbit/urbit 5a187fe, including mar/svg) and %landscape (sur/treaty, sur/docket, lib/docket, mar/docket-0; tloncorp/landscape 46c6ed8)

Outside the desk, flake.nix and flake.lock define the Nix build (see Building with Nix).

Run the tests on a ship with the desk installed:

-test /=shelf=/tests ~

Some details that are easy to get wrong:

License

MIT; see LICENSE. The vendored files keep their own notices: lib/sigil.hoon and lib/sigil/symbols.hoon are MIT (Fang; shapes from @urbit/sigil-js, Tlon), and the files from %base and %landscape come from urbit/urbit (MIT) and tloncorp/landscape.