Skip to content

Troubleshooting

Failures this project has actually hit, listed by the symptom you would see rather than by the subsystem at fault. Most of them share a shape: the error message is accurate but describes a layer you were not thinking about.

The service will not start, or cannot write

OSError: [Errno 30] Read-only file system on a directory that exists

OSError: [Errno 30] Read-only file system: '/srv/longbox/data/pages'

The directory is there, on a writable disk, owned by the right user. The errno is describing the mount namespace, not the hardware.

The unit is hardened with ProtectSystem=strict, which makes the entire filesystem read-only to the service except the paths systemd has been told to grant. Pointing LONGBOX_DATA_DIR somewhere the unit does not know about produces exactly this.

Re-run the installer, which reads /etc/longbox.env and generates the matching grant as a drop-in:

sudo ./deploy/install.sh

Same applies to LONGBOX_UPLOAD_DIR and LONGBOX_BACKUP_DIR when they sit outside the data directory. See Where the files go.

The CLI says there are no accounts, but the web UI has plenty

Two copies of a path drifted apart, and the CLI is reading a different database from the service.

This is why /etc/longbox.env carries the paths uncommented even though systemd would otherwise export them: longbox-cli reads that file too. A path known only to the unit leaves the two looking at different databases.

It is also why you should not invoke the interpreter directly:

# Wrong — picks up neither /etc/longbox.env nor the working directory
sudo /opt/longbox/.venv/bin/python -m app.cli list-users

# Right
sudo longbox-cli list-users

systemd loads EnvironmentFile= only for the units it starts, so a shell running that interpreter by hand inherits none of it and looks for a database somewhere else entirely.

If the environment is not set at all the CLI stops rather than guessing. That is deliberate: an empty database created at the wrong path would answer "there are no accounts" to the one person who needs the truth.

After an upgrade

The server still behaves like the previous release

Every file on disk is current, git log is current, the installer reported success — and the running process is the old one. Copying files over a running service does not change what it serves.

systemctl show longbox -p ActiveEnterTimestamp
sudo systemctl restart longbox

install.sh used to end with systemctl enable --now, which starts a stopped unit and does nothing to a running one. It now runs enable then restart, which is correct for a fresh install and an upgrade alike — so this only affects a host whose last upgrade predates that fix.

Nothing in the application can report this, because the application answering you is the one that never noticed. It was found by tests/live_check.py walking the deployed feed and getting phase-3 answers out of a phase-5 checkout. That is the argument for checking a deployment rather than only an application: every suite imports the code it is testing.

Sign-in and accounts

Nobody can sign in over HTTP after enabling HTTPS

LONGBOX_SECURE_COOKIES=1 stops the session cookie travelling over plain HTTP. Sign-in over http:// then stops working — deliberately, but it looks like a broken login.

Reach the server over https://, or unset the variable while you are still setting the proxy up.

One person's bad password locks out the whole household

The login throttle keys on the caller's address. Behind a reverse proxy, every request arrives from the proxy, so with no trust configured everyone shares 127.0.0.1 and one person's failures throttle everybody.

Set LONGBOX_TRUSTED_PROXIES to the proxy's address. The default is loopback, which covers a proxy on the same host; a proxy in another container or on another machine has to be named (172.18.0.0/16, say).

…but the throttle stopped working entirely

The opposite error, and the more serious one. X-Forwarded-For is a header anyone can write, so it is worth believing only when a proxy you run wrote it. Setting LONGBOX_TRUSTED_PROXIES=* believes it from everybody, at which point connecting straight to the port with a made-up value defeats the login throttle — and every OPDS token guess along with it.

Never *. Name the proxy.

LONGBOX_PUBLIC_URL is unset, so links are built from the request's Host header — which the caller chooses.

Behind a proxy this is not cosmetic. Set it.

Locked out after an upgrade

The recovery CLI needs no web UI and no third-party packages:

sudo longbox-cli list-users
sudo longbox-cli reset-password <username>

Administrators cannot set passwords by design; this prints a one-time link.

After upgrading from a single-user install, the library belongs to nobody

Expected, briefly. The 1 → 2 migration parks the existing library on an inactive placeholder account, and the first account to register adopts all of it — comics, reading state, favourites, watched folders and the Comic Vine key.

So register immediately after that upgrade, before anyone else does. Whoever is first becomes the administrator and inherits the library.

This is also why migration says not to register on the new host before restoring the database.

External sign-in fails with a redirect mismatch

The public URL changed and the OAuth redirect URIs registered with Google, Apple or Facebook still name the old one.

Update them in each provider's console. The error surfaces at the provider, so there is nothing in Longbox's logs to find.

The library looks wrong

Every comic is marked missing

The files moved on disk. Paths are stored absolutely, so the library is looking where they used to be.

Do not re-import — that strands reading state, ratings and metadata edits on the old rows. Move the files, then tell the library:

sudo longbox-cli relocate --dry-run /old/path /new/path
sudo longbox-cli relocate /old/path /new/path

An already-tagged comic keeps reappearing in the bulk-match list

Fixed, but worth recognising if you are on an older release. "Matched" means matched against any source; three queries tested comicvine_id alone, so a comic tagged from Metron looked untagged forever.

A collected edition was tagged as issue #1

Known defect, not yet fixed. Providers file collections under two different shapes — either the series' issues are the volumes, or each trade paperback is its own one-issue series named Series: Subtitle. A volume marker in the filename is read as an issue number, which is right for the first shape and wrong for the second.

So God Hates Astronauts v02 matches single issue #2, at the series name's confidence, which is well past the threshold that applies without asking.

With ComicInfo write-back enabled this gets written into the file, where a later rescan will not undo it. If you tag collections in bulk, check them.

New ComicInfo fields are empty on comics imported before an upgrade

The 3 → 4 migration adds columns and backfills portable ids, but re-reads nothing from disk. verify will not do it either — it checks bytes, not metadata.

Use Re-read file on a comic, or a rescan with Re-read files already imported.

verify reports a file changed, and it is not corrupt

Re-tagging a comic rewrites it, so the hash legitimately moves.

Longbox deliberately does not update the stored hash when it sees a change, because that would quietly bless whatever the file has become and call it clean next time. Once you know why it changed, accept it explicitly:

sudo longbox-cli verify --rehash

A CBR will not open

RAR is proprietary and Python cannot unpack it alone. Install one of the tools Longbox tries in order — 7-Zip, unar/unrar, or bsdtar. The Docker image installs unar already.

JPEG XL pages have no cover or thumbnail

Expected, and honest rather than broken. JPEG XL stays in the accepted formats so those pages still count — dropping it would make a comic import claiming fewer pages than it has, which is a quieter wrong answer than a missing cover. There is simply no decoder in the default requirements.

OPDS and feed readers

Every search returns the whole library

Including searches for comics that do not exist. The client had nothing to substitute, so it fetched the base URL — which is the unfiltered feed.

The two specifications are not the same shape: OPDS 1.2 discovers search through an OpenSearch description that substitutes {searchTerms}, while OPDS 2.0 carries an RFC 6570 template spelled {&q}. One builder served both and neither worked. Fixed; both query and q are accepted on the way in, so feeds a client already saved keep working.

If you are on an older release, this is what you are seeing.

The token authenticates the request it is in. A feed is a set of links to follow, and every link Longbox handed back used to be bare — so the first navigation entry a client opened was unauthenticated.

Basic survives this by itself, because the client re-sends the credentials. Tokens did not, which broke exactly the clients the token exists for. Fixed: every emitted URL now carries the caller's token, including the PSE page template and facet links that already have a query string.

A Google or Apple account cannot sign a reader in

Expected, and the token is the answer. A feed reader speaks HTTP Basic; an account that signs in through a provider has no password to send, and cannot run an OAuth flow either — that needs a browser, and OPDS has nowhere to put one.

Generate a token on Settings. It is the only way such an account can connect a reader at all.

The sign-in error is deliberately unhelpful — password_login() answers "Incorrect username or password" for an account that has no password, and takes the same time either way. Explaining the real reason to an unauthenticated caller would confirm the account exists.

A comic is missing from the client entirely

Check whether it has a cover. A comic whose cover extraction failed carries no image links at all in its feed entry, and at least one client (Panels) drops such an entry rather than showing it plain.

Everything else about the comic can be intact. A cover is not decoration — its absence can make a comic invisible in a reader, and neither the feed nor the web UI will tell you that.

The JPEG XL fixture is the known case, since nothing in the default requirements can decode it.

Mail

Mail is silently never delivered

Sending straight from a home connection does not work. Residential ranges sit on blocklists by default, most ISPs block outbound port 25, and you usually cannot set reverse DNS for your own address. Mail is rejected or binned without a bounce.

Send through a relay. See Sending mail.

Mail worked on the old server and stopped after migrating

The single most common migration failure. SMTP settings entered through Admin → Mail live in the database, not /etc/longbox.env, and the database takes precedence over the environment.

If you copied only the env file, they did not come with you. Copy the database, or re-enter them.

Reverse proxy and HTTPS

Certificates stop being issued

Let's Encrypt allows only a handful of failed attempts an hour, and a typo in DNS or a router forward burns through them quickly.

Use the staging service while you are still getting it working — it has generous limits and issues certificates browsers do not trust, which is fine for checking the plumbing:

acme_ca https://acme-staging-v02.api.letsencrypt.org/directory

Comment it out and reload once staging succeeds.

Devices on the LAN cannot reach the server by name

The router cannot hairpin — send a LAN request out to the public address and back in. Many cannot do this at all, and where they can it is slower and fails confusingly.

Answer the same public names with the server's private address on the LAN. See deploy/proxy/dnsmasq-lan-names.conf.

Changing the Caddyfile appears to do nothing

Validate and reload; editing the file alone changes nothing.

caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo systemctl reload caddy

The admin pages

The Server tab hangs

A stalled NFS or SMB mount among the browse roots.

Each disk-usage lookup runs in its own thread with its own timeout (3s by default), so a dead mount gets an error on its row rather than hanging the page. If the whole page stalls on an older release, this is what it was.

Free space is reported twice under different names

Not a bug. Disk usage groups by device before reporting, because the upload directory defaults to a subdirectory of the data directory and browse roots are frequently two folders on one disk. If you are seeing genuine duplicates, they are genuinely separate devices.

Backups

There is no restore command

Deliberate. Restoring has to happen with the service stopped, and a command that silently overwrote a live database would be a worse tool than doing it by hand. See Backups and restore.

A backup will not restore

Check the schema version in the filename. Restoring a newer backup onto an older release does not work — migrations are one-way.

Backups cannot be downloaded from the web interface

Deliberate. An admin session should not be enough to walk away with every credential in the database — it contains password hashes, session tokens, OAuth client secrets and your SMTP password. Getting backups off the box requires access to the box.

Development

A test run wrote into the live library and called Comic Vine for real

Fixed, and worth knowing why it happened. Overriding LONGBOX_DATA_DIR alone is not isolation. Run on the deployment host, the suites inherited its real settings, wrote sample comics into the live upload folder and used the host's API key.

bootstrap() now blanks every LONGBOX_* variable and the provider credential names before app.config is imported, including anything the .env beside the repository would supply.

Both failures went unnoticed because a machine with no .env passes either way.

A format is advertised but every file of that type fails

The CB7 lesson. Four things lined up: the library was in no requirements file, the code called a method that does not exist, the exception handler caught bare Exception and reported it as "no metadata", and no fixture existed.

The rule that came out of it: a format the app claims to support has a generated fixture and a test that reads a page out of it. Any one of those four would have caught it; the missing fixture is the one that let it survive.

Fixtures do not include a format your machine can now build

The fixture tree carries a version and capability stamp (tests/.fixtures/.fixture-version). It is rebuilt when the recipe changes or when the machine can now build something the tree lacks — never the reverse, since losing an optional library should not discard a usable collection.

If a fixture is missing, delete the stamp and re-run.