Jump to content

Shelfmark on PMSS

From Pulsed Media Wiki


Shelfmark gives a Pulsed Media seedbox one web page for finding ebooks and audiobooks: it looks up titles in a book metadata service, searches the sources you configure, such as your Prowlarr indexers, and hands what you pick to a download client. It runs in rootless Docker, which every PMSS seedbox plan includes, at your account's HTTPS address and with its own login; the current seedbox plans have 1 GB to 4 GB of memory (Seedbox and Storage Box Resource Tiers). Tested on a Pulsed Media seedbox on 24 October 2026 with ghcr.io/calibrain/shelfmark version 1.5.1.

Shelfmark login form with user name and password fields and a Sign In button
The Shelfmark login form from the 24 October 2026 test.

What the test covered: the same settings and the same proxy fragment as below, the login (a wrong password was refused, anonymous API requests through the public address got 401) and the web address. None of the numbered steps was run exactly as written: the test started from the image's latest tag (the digest below is what that tag resolved to), published a loopback port from the start and created the admin through that port. The steps below create the admin before any port is published instead. A second test the same day, on another Pulsed Media server, ran the numbered steps, the Test checks and the Uninstall commands as written; it signed in through Shelfmark's login API rather than a browser, and its fragment did not yet have the setenv line. Connecting Prowlarr and a download client, and disk use, were not tested.

Privacy

Privacy level: MEDIUM (the levels used on these guides are HIGH, MEDIUM, LOW and NONE). Using Shelfmark reduces your privacy:

  • Book searches go to the metadata service you turn on in its settings, from the server's address, so that service sees what titles you look up. Open Library is one choice; Hardcover needs an API token from your Hardcover account and Google Books an API key, which tie your searches to that account or key (Shelfmark settings reference).
  • Every source and download client you connect sees the searches and downloads sent to it. Their addresses and API keys are kept in Shelfmark's settings; upstream describes its /config folder, here ~/docker/shelfmark/config, as its config, database and artwork cache folder. The test did not inspect how keys are stored.
  • Some parts of Shelfmark answer without a login. Besides the login page itself, the test found two: /api/health answers 200 (its content was not recorded), and the live-update channel (Socket.IO) accepted an anonymous connection and sent an empty status message. Whether that channel shows anything once downloads exist was not tested, and not every endpoint was probed.
  • Other accounts on the server can reach these on 127.0.0.1. Because the fragment in step 7 passes everything under your path to Shelfmark, they are most likely reachable through your public address too.

Shelfmark needs no outside account of its own. Whether it sends usage data, and how its session cookie is scoped on the shared server address, was not checked.

Requirements

  • Memory: Docker counted 352 MiB for the container in the second test, with no source or download client connected; on the first test server its counter gave no figure for the container alone. userShowLimits (UserShowLimits on PMSS) shows your account's memory use against its limit. Upstream says the standard image needs about 2 GB when its built-in browser runs for direct downloads, and that Prowlarr searches do not start the browser. Upstream also offers a Lite image without the browser; it was not tested. Seedbox and Storage Box Resource Tiers lists each plan's memory: 1 GB on the smallest current seedbox plan, shared with the torrent client, up to 4 GB. Whether a 1 GB plan is enough on the Prowlarr route is not known.
  • Disk: the image size and file count were not measured. Shelfmark caches book covers, up to 500 MB by default. Your file quota is 500 files per GiB of disk quota (Pikkumies on PMSS gives the rule); check what you have left with quota -s.
  • Own login: yes, a local admin user. Shelfmark has no setting that creates it before the first start, so the steps below create it while the container has no published port.
  • Address: https://SERVER.pulsedmedia.com/user-USERNAME/shelfmark/, set with Shelfmark's URL_BASE setting.
  • Other apps: for the route this guide covers, Prowlarr, which Install Media Stack installs, and a download client; see Integrate.

Running and updating Shelfmark is yours to do; this page pins one build.

Install

In the commands below, replace USERNAME with your account name, SERVER with your server's name as it appears in your panel address, and PORT with a free port between 2000 and 38000: run ss -tln and pick a number that does not appear in its output. Run everything over SSH as your own account.

Shelfmark starts with no login. Other accounts on the server can connect to 127.0.0.1, so the admin user is created while the container has no port at all, and the port is published only after the login is switched on.

1. Create the folders and close them to other accounts. books is where downloads land:

mkdir -p ~/docker/shelfmark/config ~/docker/shelfmark/books
chmod 700 ~/docker/shelfmark

2. Write the settings file, with the login still off for setup. PUID=0 and PGID=0 make the files Shelfmark writes belong to your own account:

printf 'PUID=0\nPGID=0\nTZ=Etc/UTC\nAUTH_METHOD=none\nURL_BASE=/user-USERNAME/shelfmark\n' > ~/docker/shelfmark/app.env
chmod 600 ~/docker/shelfmark/app.env

3. Generate the admin password and the request that creates the admin, both in files only you can read:

head -c 24 /dev/urandom | base64 | tr -d '\n' > ~/docker/shelfmark/admin.pw
chmod 600 ~/docker/shelfmark/admin.pw
printf '{"username":"admin","password":"%s","role":"admin"}' "$(cat ~/docker/shelfmark/admin.pw)" > ~/docker/shelfmark/admin.json
chmod 600 ~/docker/shelfmark/admin.json

Read the password later with cat ~/docker/shelfmark/admin.pw.

4. Start a setup container with no published port. The image is pinned to the digest the tested version had:

docker run -d --name shelfmark-setup --env-file ~/docker/shelfmark/app.env \
  -v ~/docker/shelfmark/config:/config -v ~/docker/shelfmark/books:/books \
  ghcr.io/calibrain/shelfmark@sha256:acfa46b8be80f1e2f084a7703ff027ab274397feb24006114e92c05c140b62c3

Wait until docker ps shows it as (healthy), which can take a minute or two.

5. Create the admin from inside the container. The image includes curl for its own health check. The second line removes the request file and the setup container only if the admin was created (status 201):

code=$(docker exec -i shelfmark-setup curl -s -o /dev/null -w '%{http_code}' -H 'Content-Type: application/json' --data-binary @- http://127.0.0.1:8084/user-USERNAME/shelfmark/api/admin/users < ~/docker/shelfmark/admin.json); echo "$code"
[ "$code" = 201 ] && rm ~/docker/shelfmark/admin.json && docker rm -f shelfmark-setup

If it printed anything other than 201, see Troubleshooting; the setup container and admin.json are still there.

6. Switch the login on and start Shelfmark on the loopback address. The docker run only starts if step 5 finished (no admin.json left) and the settings file has the login on:

sed -i 's/^AUTH_METHOD=none$/AUTH_METHOD=builtin/' ~/docker/shelfmark/app.env
test ! -e ~/docker/shelfmark/admin.json && grep -qx 'AUTH_METHOD=builtin' ~/docker/shelfmark/app.env && \
  docker run -d --name shelfmark --restart unless-stopped --env-file ~/docker/shelfmark/app.env \
  -p 127.0.0.1:PORT:8084 -v ~/docker/shelfmark/config:/config -v ~/docker/shelfmark/books:/books \
  ghcr.io/calibrain/shelfmark@sha256:acfa46b8be80f1e2f084a7703ff027ab274397feb24006114e92c05c140b62c3

If nothing starts, either admin.json is still there (step 5 did not finish) or app.env does not contain AUTH_METHOD=builtin.

7. Add the proxy fragment. auth.require = () hands the login to Shelfmark, setenv.set-request-header keeps your panel login (which a browser logged into your panel sends with every request) away from it, and "upgrade" => "enable" lets WebSocket connections through, which Shelfmark uses for live updates. There is no map-urlpath, because URL_BASE already makes Shelfmark answer under your path:

cat > ~/.lighttpd/custom.d/shelfmark.conf << 'EOF'
$HTTP["url"] =~ "^/user-USERNAME/shelfmark/" {
  auth.require = ()
  setenv.set-request-header = ( "Authorization" => "" )
  proxy.server = ( "" => ( ( "host" => "127.0.0.1", "port" => PORT ) ) )
  proxy.header = ( "upgrade" => "enable" )
}
EOF
/usr/sbin/lighttpd -t -f ~/.lighttpd.conf

The last command checks the syntax and prints Syntax OK. If it prints an error instead, fix or delete shelfmark.conf straight away: a broken fragment keeps your web panel down at the next restart. Your web server picks the file up within a minute or two, and the panel and proxied apps are unavailable for a moment while it restarts.

8. Open https://SERVER.pulsedmedia.com/user-USERNAME/shelfmark/, with the trailing slash, and sign in as admin with the password from admin.pw.

Configure

  • Sources: this guide covers one route, Prowlarr. Turn the Prowlarr source on in Shelfmark's settings and give it your Prowlarr address and API key (in Prowlarr: Settings → General → API Key). No address a container can use to reach your Prowlarr has been tested; see Integrate. Shelfmark can also use other source types; they are not covered here.
  • Download client: choose the client that should fetch what Prowlarr finds, and its address and login.
  • Where books go: downloads land in ~/docker/shelfmark/books, which is /books inside the container.
  • Metadata service: turn on the one you want to look titles up with; see Privacy.
  • Updating: the digest in steps 4 and 6 keeps you on the build tested on 24 October 2026. To move to a newer build, find the digest the image's tag points to now, then run docker rm -f shelfmark and step 6's docker run again with that digest; ~/docker/shelfmark/config keeps your settings and users. Updating was not tested.
docker pull ghcr.io/calibrain/shelfmark:latest
docker image inspect ghcr.io/calibrain/shelfmark:latest --format '{{index .RepoDigests 0}}'

Integrate

Not tested on Pulsed Media servers, and this page does not give you the two settings that the route needs:

  • Reaching Prowlarr: inside a container, 127.0.0.1 is the container itself, not the server. No address that a container can use to reach a Prowlarr running in your account has been tested on Pulsed Media servers.
  • Download client paths: when a torrent or Usenet client does the download, Shelfmark's upstream instructions say to mount the client's download folder into the container at exactly the same path the client uses. That means another -v mount on the docker run in step 6; it was not tested.

Shelfmark's settings reference lists every option.

Test

  • docker ps lists shelfmark as Up and (healthy).
  • https://SERVER.pulsedmedia.com/user-USERNAME/shelfmark/ shows the Shelfmark login page, styled as in the picture above. A wrong password is refused.
  • A request with no login is refused: curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:PORT/user-USERNAME/shelfmark/api/status should print 401, which is what another account on the server would see. The test saw 401 for this endpoint through the public address; this direct form was not run.
  • stat -c '%a %n' ~/docker/shelfmark ~/docker/shelfmark/app.env ~/docker/shelfmark/admin.pw shows 750, 600 and 600. The folder shows 750, not the 700 set in step 1, because PMSS resets the folders in your home to 750 with your account's own group, so other accounts still cannot open it.

Uninstall

This removes the containers and the web address and leaves your files alone:

docker rm -f shelfmark shelfmark-setup
rm ~/.lighttpd/custom.d/shelfmark.conf
touch ~/.lighttpd/custom

If shelfmark-setup was already removed in step 5, Docker may report that it does not exist; that is harmless. The touch makes your web server restart without the removed fragment (deleting or disabling a fragment explains why).

Removing the data is your choice, and it includes your books: it deletes Shelfmark's settings, users and stored API keys, app.env, admin.pw and everything in ~/docker/shelfmark/books. Move any books you want to keep first:

rm -r ~/docker/shelfmark && docker rmi ghcr.io/calibrain/shelfmark@sha256:acfa46b8be80f1e2f084a7703ff027ab274397feb24006114e92c05c140b62c3

Both removals were run in the second test and worked. With PUID=0 the files should belong to your account; if rm still reports "Permission denied", the image is kept, and you can delete the folder's contents from inside a container first, as Webtop on PMSS shows.

Troubleshooting

  • Step 5 prints 000 or a connection error: Shelfmark was not ready. Wait until docker ps shows (healthy) and run the two lines of step 5 again. If the setup container stopped, read docker logs shelfmark-setup.
  • Step 5 prints 401 or 403: most likely the setup container is not running with AUTH_METHOD=none. Check app.env, run docker rm -f shelfmark-setup and start again from step 4.
  • Step 5 prints another code: run the same docker exec command without -o /dev/null to see Shelfmark's message.
  • docker run fails with "disk quota exceeded" while unpacking: usually the account ran out of files, not space, as other large images did on the test account. Check quota -s and remove images you no longer use (docker images, then docker rmi).
  • Pages load without styling, or links point outside your path: this did not happen in the test. Check that URL_BASE in app.env is /user-USERNAME/shelfmark and that the fragment is exactly as in step 7. After changing app.env, run docker rm -f shelfmark and step 6's docker run again; a container reads its settings file only when it is created.
  • Anyone can open it without signing in: the container was created while AUTH_METHOD was still none. Run docker rm -f shelfmark, fix app.env and run step 6 again. Then check the user list in Shelfmark's settings for users you did not create, and replace any API keys you had entered.
  • The browser asks for your panel login before Shelfmark's own page: the fragment is missing auth.require = (), so your panel login sits in front of Shelfmark.
  • Live updates never appear: live updates through this address were not tested. Upstream documents a SOCKETIO_ALLOWED_ORIGINS setting for proxies that change the request's host.
  • 502 Bad Gateway: the container is not running or the port in the fragment is wrong; see the 502 section of Lighttpd Custom Configuration. A 502 for a few seconds right after you save a fragment is the web server restarting.
  • The address answers 404 minutes after you saved the fragment: the web server has not picked the fragment up yet, and until it does your panel answers the address with 404. Run touch ~/.lighttpd/custom. In the second test the fragment was still not live after about 9 minutes; after the touch it was live in about 20 seconds, while another fragment on the same account had gone live in about 90 seconds without it.

See also