A personal documents server, Coreander indexes the documents (EPUBs and PDFs with no DRM) that it finds in the passed folder, and provides a web interface to search and access them.
- Single binary with all dependencies included. Just download and run, no installation required.
- Search by author, title and even document series (Calibre's
seriesmeta supported) - Improved search for documents with metadata in English, Spanish, French, Italian, German and Portuguese, including genre and singular/plural forms of words in the results among others.
- Estimated reading time calculation.
- Responsive web interface available in English, Spanish, German, Russian and French, more languages can be easily added.
- New documents added or removed to/from the library folder are automatically indexed (Linux only).
- Send to email supported.
- Read indexed epubs and PDFs from Coreander's interface thanks to foliate-js.
- Reading progress sync between multiple devices, E.G.: start reading in your cellphone and resume reading from your tablet where you left off.
- Restrictable access only to registered users.
- Upload documents through the web interface.
- Download as kepub (epub for Kobo devices) converted on the fly thanks to Kepubify.
- Gather information about authors from Wikidata.
Binaries for Windows 64 bit, Linux 32 and 64 bits for both X86 and ARM (Raspberry Pi and other SBCs) and Macs with Intel and Apple processors are available at releases. Just download and unzip the one appropriate for your system.
Coreander's only requirement is the Go compiler. The minimum required Go version is stated in the go.mod file.
When cloning Coreander's repository, use Git's --recurse-submodules flag to also retrieve foliate-js contents as well, which is required for the reader component:
git clone git@github.com:svera/coreander.git --recurse-submodules
There are two possibilities for building Coreander from source:
- If you have Mage installed in your system, just type
mage installfrom the source code folder. - Otherwise, a simple
go buildorgo installwill do, although no version information will be added to the executable.
Although it can be executed as any desktop app, Coreander is designed to be run as a service managed by systemd or any other service manager. For example, in Raspberry Pi OS, just create a file called /etc/systemd/system/coreander.service with the following contents:
[Unit]
Description=coreander
[Service]
Restart=always
RestartSec=5s
WorkingDirectory=<absolute path to directory which contains coreander binary>
ExecStart=<absolute path to coreander binary>
PermissionsStartOnly=true
SyslogIdentifier=coreander
User=<user which will execute this service>
Environment="LIB_PATH=<absolute path to the library>"
then, start the service with service coreander start. You can manage it with the usual commands start, stop and status. Refer to your service manager documentation for more information.
Coreander requires the absolute path where your documents are located as an argument. You can also pass it through the LIB_PATH environment variable.
On first run, Coreander will index the documents in your library, creating a database with those entries located at $home/coreander/index. Depending on your system's performance and the size of your library this may take a while. Also, the database can grow fairly big, so make sure you have enough free space on disk.
Every time it is run, the application scans the library folder only for documents not yet indexed and adds them to the index. You can force to index all documents whether they were previously indexed or not by passing the --force-indexing flag or setting the environment variable FORCE_INDEXING to true.
Even if the application is still indexing entries, you can access its web interface right away. Just open a web browser and go to localhost:3000 (replace localhost with the hostname / IP address of the machine where the server is running if you want to access it from another system). It is possible to change the listening port just executing the application with the -p or --port flags, or the PORT environment variable (e. g. coreander -p 4000 or PORT=4000 coreander)
If you plan to set up Coreander in a public Internet server such as a VPS, using Caddy as a reverse proxy is strongly recommended, as it is dead simple to set up and comes with several niceties such as HTTPS out of the box through Let's Encrypt.
For example, if you have Coreander listening to port 3000 in your server and a domain called coreander.example.com that points to the IP address of your server, you can just tell Caddy to route requests to coreander.example.com to Coreander by putting this in Caddy's Caddyfile:
coreander.example.com {
reverse_proxy :3000
}
Refer to Caddy documentation for more information.
Some features rely on having an SMTP email service set up, and won't be available otherwise:
- Send document to email.
- Recover user password.
- Invite new users.
You can use any email service that allows sending emails using the SMTP protocol, like GMX. The following flags or environment variables need to be defined:
| Flag | Environment variable | Description |
|---|---|---|
--smtp-server |
SMTP_SERVER |
The URL of the SMTP server to be used, for example mail.gmx.com. |
--smtp-port |
SMTP_PORT |
The port number used by the email service, defaults to 587. |
--smtp-user |
SMTP_USER |
The user name. |
--smtp-password |
SMTP_PASSWORD |
User's password. |
Coreander can send documents through email. This way, you can take advantage of services such as Amazon's send to email, which also automatically converts EPUB and other formats to the target device.
Coreander distinguishes between two kinds of users: regular users and administrator users, with the latter being the only ones with the ability to create new users and upload and delete documents.
By default, Coreander allows unrestricted access to its contents, except management areas which require an administrator user. To allow access only to registered users in the whole application, pass the -a or --require-auth flags, or the REQUIRE_AUTH=true environment variable.
On first run, Coreander creates an admin user with the following credentials:
- Email:
admin@example.com - Password:
admin
Caution
For security reasons, it is strongly encouraged to add a new admin and remove the default one as soon as possible.
Coreander uses TextRank to automatically extract keywords (single words and two-word phrases) from the text of EPUB documents during indexing. TextRank builds a graph where words are nodes and an edge connects two words whenever they appear near each other in the text; words that co-occur with many other important words end up with a higher rank, similarly to how Google's PageRank ranks web pages by how many other important pages link to them. The highest-ranked words and phrases become a document's keywords.
Note
This analysis process may take up to several hours depending on the host system and the size of the library.
These extracted keywords power two features:
- Search: keywords are indexed alongside title, author and other metadata, so a document can be found by searching for a term that appears frequently in its text even if it's not part of its declared metadata.
- Similar documents: when viewing a document, Coreander suggests others that share a meaningful number of its top keywords.
Before ranking, Coreander detects the document's language(s) (falling back to full text detection if the EPUB doesn't declare one) and filters out stop words (common words like "the" or "and" that carry no distinctive meaning) for each detected language, plus English stop words always, since documents often mix in English terms regardless of their main language.
Not every word or phrase TextRank finds is kept: only those whose occurrence count is close enough to the most frequent one survive, controlled by --min-occurrence-ratio (see table below). This avoids keeping words that only appear once or twice in an otherwise repetitive document, which would otherwise look important simply because they're compared against a low baseline.
Separately, once keywords exist across the whole library, any phrase or word that turns out to be too common there (a genre-wide word, or a series' recurring character name) is stripped from every document that has it, since a signal shared by a large fraction of the library isn't distinctive enough for keyword search or "similar documents" to rely on. This whole-library pruning pass runs after every full (re)indexing, and again in the background whenever enough documents are added or removed since the last pass, controlled by --common-textrank-entry-ratio, --min-common-textrank-absolute-count and --prune-change-trigger-ratio (see table below).
You can fine-tune this behavior with the following flags:
| Flag | Environment variable | Description |
|---|---|---|
--min-occurrence-ratio |
MIN_OCCURRENCE_RATIO |
Raise it to keep only the most frequent, most representative keywords per document (fewer, more precise search/similarity matches); lower it (down to 0, which disables text ranking altogether) to keep more of the long tail of less frequent keywords (broader matches, more noise). |
--max-similarity-phrases |
MAX_SIMILARITY_PHRASES |
Raise it so documents with many phrases are matched more accurately when looking for similar documents, at the cost of slower queries; lower it for faster queries that only rely on each document's most important phrases. |
--max-similarity-candidates |
MAX_SIMILARITY_CANDIDATES |
Doesn't affect TextRank extraction itself, but controls how strict the "similar documents" feature is once keywords exist. Maximum number of top-scoring matches a "similar document" query considers before pruning by min-similarity-score-ratio and paginating. Higher values lower the chance of a genuinely similar document being cut off before scoring, at the cost of slower queries; lower values speed queries up but risk missing weaker true matches. Defaults to 200. |
--min-similarity-score-ratio |
MIN_SIMILARITY_SCORE_RATIO |
Doesn't affect TextRank extraction itself, but controls how strict the "similar documents" feature is once keywords exist. Minimum fraction of the best match's score a document must reach to be considered similar enough to show in a "similar document" query. Higher values give fewer but more relevant results (a "similar documents" list can end up empty); lower values show more results but risk weak, coincidental matches. Defaults to 0.3. |
--max-textrank-words |
MAX_TEXTRANK_WORDS |
Caps how many words of a document's text TextRank analysis considers. If a document's text is longer, only its first max-textrank-words words are analyzed, and its keywords end up based on that opening portion rather than the whole text (its word count still reflects the whole document). TextRank builds an in-memory graph that grows with every word occurrence, not just distinct words, so a very long or repetitive document can use a large amount of RAM for a single document, even without concurrent indexing. Defaults to -1, which computes a safe value automatically from the host's total RAM and the number of indexing workers (see --index-workers); set it to a positive number to override that with an explicit cap, or to 0 to disable the cap entirely. |
--common-textrank-entry-ratio |
COMMON_TEXTRANK_ENTRY_RATIO |
Raise it so only phrases/words shared by a larger fraction of the library get pruned as "too common" (less aggressive pruning, more keywords kept); lower it to prune more aggressively. |
--min-common-textrank-absolute-count |
MIN_COMMON_TEXTRANK_ABSOLUTE_COUNT |
Raise it so small libraries need more documents sharing a phrase/word before it's considered common enough to prune; mainly relevant for small libraries, where a low document count could otherwise make a ratio-based threshold trigger on just a couple of shared documents. |
--prune-change-trigger-ratio |
PRUNE_CHANGE_TRIGGER_RATIO |
Raise it so the whole-library pruning pass only re-runs in the background after a bigger fraction of the library has changed since the last pass; lower it to keep common-entry statistics fresher at the cost of more frequent background scans. |
Since keyword extraction runs once per document during indexing, changing --min-occurrence-ratio only affects documents indexed (or re-indexed with --force-indexing) after the change.
Run coreander -h or coreander --help to see help.
In case both a flag and its equivalent environment variable are passed, flag takes precedence.
| Flag | Environment variable | Description |
|---|---|---|
LIB_PATH |
Absolute path to the folder containing the documents. | |
-p or --port |
PORT |
Port number in which the webserver listens for requests. Defaults to 3000. |
-b or --batch-size |
BATCH_SIZE |
Number of documents persisted by the indexer in one write operation. Defaults to 100. |
--index-workers |
INDEX_WORKERS |
Parallel workers for metadata extraction during indexing. 0 (default) uses an automatic count based on CPUs (capped at 64); 1 is sequential; 2 or higher sets an explicit pool size (also capped at 64). |
--cover-max-width |
COVER_MAX_WIDTH |
Maximum horizontal size for documents cover thumbnails in pixels. Defaults to 600. |
--author-image-max-width |
AUTHOR_IMAGE_MAX_WIDTH |
Maximum horizontal size for author images in pixels. Set to 0 to keep original image size. Defaults to 600. |
--illustrated-min-amount |
ILLUSTRATED_MIN_AMOUNT |
Minimum number of illustrations (excluding cover) for a document to be considered illustrated. Only raster images in PNG, GIF and JPEG formats are taken into account. Defaults to 2. |
--illustrated-min-size |
ILLUSTRATED_MIN_SIZE |
Minimum size in megapixels for an image to count as an illustration. Defaults to 0.25. |
-c or --cache-dir |
CACHE_DIR |
Directory where to store cache files. Defaults to ~/.coreander/cache. |
--cache-max-size |
CACHE_MAX_SIZE |
Maximum total size of the cache directory in megabytes. Oldest files are evicted first when the limit is reached. Set to 0 for unlimited. Defaults to 500. |
--client-static-cache-ttl |
CLIENT_STATIC_CACHE_TTL |
Client-side cache duration for static assets (CSS, JS, images) in seconds. Defaults to 31536000 (1 year). |
--client-dynamic-image-cache-ttl |
CLIENT_DYNAMIC_IMAGE_CACHE_TTL |
Client-side cache duration for dynamically generated images (covers, author images) in seconds. Defaults to 86400 (24 hours). |
--server-static-cache-ttl |
SERVER_STATIC_CACHE_TTL |
Server-side cache duration for static assets (CSS, JS, images) in seconds. Defaults to 31536000 (1 year). |
--server-dynamic-image-cache-ttl |
SERVER_DYNAMIC_IMAGE_CACHE_TTL |
Server-side cache duration for dynamically generated images (covers, author images) in seconds. Defaults to 86400 (24 hours). |
-f or --force-indexing |
FORCE_INDEXING |
Whether to force indexing already indexed documents or not. Defaults to false. |
-s or --jwt-secret |
JWT_SECRET |
String to use to sign JWTs. |
-a or --require-auth |
REQUIRE_AUTH |
Require authentication to access the application if true. Defaults to false. |
--min-password-length |
MIN_PASSWORD_LENGTH |
Minimum length acceptable for passwords. Defaults to 5. |
--words-per-minute |
WORDS_PER_MINUTE |
Defines a default words per minute reading speed that will be used for not logged-in users. Defaults to 250. |
--session-timeout |
SESSION_TIMEOUT |
Specifies the maximum time a user session may last, in hours. Floating-point values are allowed. Defaults to 24 hours. |
--recovery-timeout |
RECOVERY_TIMEOUT |
Specifies the maximum time a user recovery link may last, in hours. Floating-point values are allowed. Defaults to 2 hours. |
--invitation-timeout |
INVITATION_TIMEOUT |
Specifies the maximum time a user invitation link may last, in hours. Floating-point values are allowed. Defaults to 72 hours. |
--invite-email-list-max-length |
INVITE_EMAIL_LIST_MAX_LENGTH |
Maximum length in bytes of the comma-separated invitation email list field (admin invite form). Defaults to 2000. |
--invite-max-recipients |
INVITE_MAX_RECIPIENTS |
Maximum number of distinct email addresses allowed in one invitation submit. Defaults to 50. |
-u or --upload-document-max-size |
UPLOAD_DOCUMENT_MAX_SIZE |
Maximum document size allowed to be uploaded to the library, in megabytes. Set this to 0 to unlimit upload size. Defaults to 20 megabytes. |
-m or --share-comment-max-size |
SHARE_COMMENT_MAX_SIZE |
Maximum length for share comments in characters. Defaults to 280. |
--share-max-recipients |
SHARE_MAX_RECIPIENTS |
Maximum number of recipients allowed when sharing a document. Defaults to 10. |
-d or --fqdn |
FQDN |
Domain name of the server. If Coreander is listening to a non-standard HTTP / HTTPS port, include it using a colon (e. g. example.com:3000). Defaults to localhost. |
-v or --version |
Show version number. |