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.
Password reset links point at the wrong host¶
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.
A token works at the root, then 401s on the first link¶
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.