The long-standing open question "where will the server actually live" is answered for now: it stays on this sprite, bound to 0.0.0.0:8090 and published over HTTPS by the sprite proxy. Several docs asserted the opposite — HANDOFF said "127.0.0.1:8090, deliberately NOT internet-exposed (no --http-port, so the sprite proxy can't reach it)", which is flatly wrong today. The consequence is the part worth writing down: PocketBase's API rules are now the only thing between this library and the internet. There is no NAT, no VPN, no reverse proxy. So the anonymous-access curls stop being a formality, and they have to run against the PUBLIC hostname — localhost cannot tell you what the world can reach. Re-verified that way: books/shelves/bookcases LIST all 403, self-registration 403, health 200. One gap found while re-verifying, recorded but NOT fixed: users LIST answers 200 with an empty array instead of 403. Nothing is disclosed — two real accounts exist and the listRule filters both out — but it is the same wrong-signal quirk pb_hooks/main.pb.js exists to close, and that hook never listed the users collection. SPEC's offline-first rationale is amended rather than its rule: the reason is no longer residential NAT but a sprite that suspends when idle and wakes on request. The rule is unchanged and does not depend on which. server/deploy/ still documents systemd/Docker/Tailscale on home hardware; it now says up front that this is the intended end state, not what is running. Also corrected, since it was adjacent and plainly false: README still claimed the app had never run on a physical device. It has, since 2026-09-09. What is true is that no *automated* test runs on a device — there is no emulator on this box. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPpdG8VnRfS3KkisR3HUAE
166 lines
6.5 KiB
Markdown
166 lines
6.5 KiB
Markdown
# Deploying Bookshelf's PocketBase server
|
|
|
|
> **This is not what is running today.** The live server currently runs on the
|
|
> development sprite, bound to `0.0.0.0:8090` and published over HTTPS on a
|
|
> sprite-issued hostname — **internet-exposed**, with PocketBase's API rules as
|
|
> its only protection (see `../README.md` § "Where the live server runs"). This
|
|
> document describes the *intended* end state: your own hardware at home, not
|
|
> directly exposed. Where it says PocketBase is bound to localhost and
|
|
> unreachable from outside, that is a statement about the deployment described
|
|
> here, not about the instance the phones are currently pointed at.
|
|
|
|
This assumes a spare always-on machine at home (a mini PC, NUC, Raspberry Pi,
|
|
or an old laptop) running Linux. Pick **one** of the two run methods below —
|
|
systemd or Docker — not both.
|
|
|
|
## 0. Get the files onto the server
|
|
|
|
Copy the whole `server/` directory (minus `.dev-credentials`, which is
|
|
gitignored and dev-only) to the target machine, e.g.:
|
|
|
|
```sh
|
|
rsync -av --exclude .dev-credentials ~/bookshelf/server/ youruser@homeserver:/opt/bookshelf/
|
|
```
|
|
|
|
The `pocketbase` binary is architecture-specific — if your home server isn't
|
|
the same CPU architecture as wherever you built/downloaded it, grab the
|
|
matching build from https://github.com/pocketbase/pocketbase/releases
|
|
(this project is pinned to **v0.40.2**) and drop it in as `/opt/bookshelf/pocketbase`.
|
|
|
|
## 1. Run it — Option A: systemd (recommended for a bare-metal/VM host)
|
|
|
|
1. Create a dedicated unprivileged user:
|
|
```sh
|
|
sudo useradd --system --home /opt/bookshelf --shell /usr/sbin/nologin bookshelf
|
|
sudo chown -R bookshelf:bookshelf /opt/bookshelf
|
|
```
|
|
2. Install the unit:
|
|
```sh
|
|
sudo cp deploy/bookshelf.service /etc/systemd/system/
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now bookshelf
|
|
```
|
|
3. Check it's up: `systemctl status bookshelf` and `curl http://127.0.0.1:8090/api/health`.
|
|
4. Logs: `journalctl -u bookshelf -f`.
|
|
|
|
Note the unit binds PocketBase to `127.0.0.1:8090` only — it is **not**
|
|
reachable from other machines yet. That's intentional; see step 4.
|
|
|
|
## 2. Run it — Option B: Docker
|
|
|
|
```sh
|
|
cd deploy
|
|
docker compose up -d --build
|
|
docker compose logs -f
|
|
```
|
|
|
|
This also only publishes to `127.0.0.1:8090` on the host, for the same
|
|
reason. Data persists in the `bookshelf_pb_data` named volume regardless of
|
|
container restarts/rebuilds.
|
|
|
|
## 3. Provision the schema and accounts
|
|
|
|
Once the server is up and answering on `127.0.0.1:8090`, run the setup
|
|
scripts from `server/` (one directory up from here):
|
|
|
|
```sh
|
|
cd /opt/bookshelf
|
|
|
|
# If this is a brand-new PocketBase data dir, it prints a one-time setup URL
|
|
# on first launch (see `journalctl -u bookshelf` or `docker compose logs`) —
|
|
# open that in a browser first to create your superuser account.
|
|
|
|
./setup-schema.sh http://127.0.0.1:8090 <superuser-email> <superuser-password>
|
|
|
|
# Then create the household accounts — there is no public sign-up:
|
|
./create-user.sh owner@example.com "a strong password" "Owner Name"
|
|
./create-user.sh spouse@example.com "a different strong password" "Spouse Name"
|
|
```
|
|
|
|
`setup-schema.sh` is idempotent — safe to re-run any time (e.g. after
|
|
pulling an updated `server/` if the schema ever changes).
|
|
|
|
## 4. Reach it from outside your home network
|
|
|
|
The app needs a URL it can hit from anywhere your phone goes — not just your
|
|
home wifi. Two real options:
|
|
|
|
### Tailscale (recommended)
|
|
|
|
Install Tailscale on the home server and on your phone, join both to the
|
|
same tailnet. The server gets a stable `100.x.y.z` address (or a
|
|
MagicDNS name like `homeserver.your-tailnet.ts.net`) reachable from
|
|
anywhere, encrypted end-to-end, with **no ports opened on your router**.
|
|
|
|
- **Pros:** essentially zero attack surface (nothing is exposed to the
|
|
public internet at all — not even a login page), no TLS cert management,
|
|
works behind CGNAT, five-minute setup.
|
|
- **Cons:** both devices need the Tailscale app installed and signed in;
|
|
if Tailscale's coordination service has an outage, *new* connections may
|
|
be briefly unable to establish (existing ones keep working) — acceptable
|
|
for a two-person home library, not for something needing five-nines.
|
|
- Point the app's server-URL field at `http://100.x.y.z:8090` (Tailscale's
|
|
encryption makes plain HTTP tolerable *inside the tailnet*, but see the
|
|
HTTPS note below — using `https://` via Tailscale Serve, next, is easy
|
|
enough to just do).
|
|
- Even better: use [`tailscale serve`](https://tailscale.com/kb/1312/serve)
|
|
to get automatic HTTPS with a real cert on your tailnet domain, so the
|
|
app can just always use `https://`:
|
|
```sh
|
|
sudo tailscale serve --bg 8090
|
|
```
|
|
|
|
### Port-forward + Caddy (reverse proxy with TLS)
|
|
|
|
Forward a port on your router to the home server, and run
|
|
[Caddy](https://caddyserver.com/) in front of PocketBase to terminate TLS
|
|
with an automatic Let's Encrypt certificate.
|
|
|
|
- **Pros:** works with any client, no extra app/agent needed on the phone,
|
|
a real public HTTPS URL.
|
|
- **Cons:** you're now running an internet-facing service from your home —
|
|
bugs in PocketBase, Caddy, or your router's firmware are now a real
|
|
attack surface; you need a domain name (or dynamic-DNS if your ISP gives
|
|
you a changing IP) for Let's Encrypt's HTTP-01/TLS-ALPN-01 challenge to
|
|
work; residential ISPs sometimes block inbound 80/443 or use CGNAT,
|
|
which breaks this approach entirely (Tailscale sidesteps that).
|
|
|
|
Minimal Caddyfile:
|
|
```
|
|
bookshelf.yourdomain.com {
|
|
reverse_proxy 127.0.0.1:8090
|
|
}
|
|
```
|
|
Caddy handles the certificate automatically. Point your router's port
|
|
forward at the Caddy host's 443, and the app's server-URL field at
|
|
`https://bookshelf.yourdomain.com`.
|
|
|
|
### HTTPS is not optional
|
|
|
|
The app sends the account password on every login. **Never** point the app
|
|
at a plain `http://` URL that crosses the public internet or an untrusted
|
|
network — only plain HTTP over Tailscale (which is already
|
|
end-to-end-encrypted at the network layer) is acceptable, and even there,
|
|
prefer `tailscale serve` for a real cert. If you go the port-forward route,
|
|
Caddy above gets you HTTPS for free — don't skip it.
|
|
|
|
## 5. Back up regularly
|
|
|
|
```sh
|
|
./deploy/backup.sh
|
|
```
|
|
|
|
See the comments at the top of `backup.sh` for what it does (SQLite-safe
|
|
snapshot + uploaded covers, tarball, prune old ones) and the restore
|
|
procedure. Wire it into cron:
|
|
|
|
```sh
|
|
crontab -e
|
|
# add:
|
|
0 3 * * * /opt/bookshelf/deploy/backup.sh >> /var/log/bookshelf-backup.log 2>&1
|
|
```
|
|
|
|
Consider also copying the resulting tarballs off-box (another machine, a
|
|
USB drive, cloud storage) — a backup that lives on the same disk as the
|
|
data it's backing up doesn't protect against disk failure.
|