grimmory-self-hosted-library
Expert knowledge for setting up, configuring, and extending Grimmory — a self-hosted book library manager supporting EPUBs, PDFs, comics, Kobo sync, OPDS, and multi-user management.
What this skill does
# Grimmory Self-Hosted Library Manager
> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.
Grimmory is a self-hosted application (successor to BookLore) for managing your entire book collection. It supports EPUBs, PDFs, MOBIs, AZW/AZW3, and comics (CBZ/CBR/CB7), with a built-in browser reader, annotations, Kobo/OPDS sync, KOReader progress sync, metadata enrichment, and multi-user support.
---
## Installation
### Requirements
- Docker and Docker Compose
### Step 1: Create `.env`
```ini
# Application
APP_USER_ID=1000
APP_GROUP_ID=1000
TZ=Etc/UTC
# Database
DATABASE_URL=jdbc:mariadb://mariadb:3306/grimmory
DB_USER=grimmory
DB_PASSWORD=${DB_PASSWORD}
# Storage: LOCAL (default) or NETWORK
DISK_TYPE=LOCAL
# MariaDB
DB_USER_ID=1000
DB_GROUP_ID=1000
MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE=grimmory
```
### Step 2: Create `docker-compose.yml`
```yaml
services:
grimmory:
image: grimmory/grimmory:latest
# Alternative registry: ghcr.io/grimmory-tools/grimmory:latest
container_name: grimmory
environment:
- USER_ID=${APP_USER_ID}
- GROUP_ID=${APP_GROUP_ID}
- TZ=${TZ}
- DATABASE_URL=${DATABASE_URL}
- DATABASE_USERNAME=${DB_USER}
- DATABASE_PASSWORD=${DB_PASSWORD}
- DISK_TYPE=${DISK_TYPE}
depends_on:
mariadb:
condition: service_healthy
ports:
- "6060:6060"
volumes:
- ./data:/app/data
- ./books:/books
- ./bookdrop:/bookdrop
healthcheck:
test: wget -q -O - http://localhost:6060/api/v1/healthcheck
interval: 60s
retries: 5
start_period: 60s
timeout: 10s
restart: unless-stopped
mariadb:
image: lscr.io/linuxserver/mariadb:11.4.5
container_name: mariadb
environment:
- PUID=${DB_USER_ID}
- PGID=${DB_GROUP_ID}
- TZ=${TZ}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${DB_USER}
- MYSQL_PASSWORD=${DB_PASSWORD}
volumes:
- ./mariadb/config:/config
restart: unless-stopped
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10
```
### Step 3: Launch
```bash
docker compose up -d
# View logs
docker compose logs -f grimmory
# Check health
curl http://localhost:6060/api/v1/healthcheck
```
Open http://localhost:6060 and create your admin account.
---
## Volume Layout
```
./data/ # App data, thumbnails, user config
./books/ # Your book files (mounted at /books)
./bookdrop/ # Drop-zone for auto-import (mounted at /bookdrop)
./mariadb/ # MariaDB data
```
---
## Environment Variables Reference
| Variable | Description | Default |
|---|---|---|
| `USER_ID` | UID for the app process | `1000` |
| `GROUP_ID` | GID for the app process | `1000` |
| `TZ` | Timezone string | `Etc/UTC` |
| `DATABASE_URL` | JDBC connection string | required |
| `DATABASE_USERNAME` | DB username | required |
| `DATABASE_PASSWORD` | DB password | required |
| `DISK_TYPE` | `LOCAL` or `NETWORK` | `LOCAL` |
---
## Supported Book Formats
| Category | Formats |
|---|---|
| eBooks | EPUB, MOBI, AZW, AZW3 |
| Documents | PDF |
| Comics | CBZ, CBR, CB7 |
---
## BookDrop (Auto-Import)
Drop files into `./bookdrop/` on your host. Grimmory watches the folder, extracts metadata from Google Books and Open Library, and queues books for review.
```
./bookdrop/
my-novel.epub ← dropped here
another-book.pdf ← dropped here
```
Flow:
1. **Watch** — Grimmory monitors `/bookdrop` continuously
2. **Detect** — New files are picked up and parsed
3. **Enrich** — Metadata fetched from Google Books / Open Library
4. **Import** — Review in UI, adjust if needed, confirm import
Volume mapping required in `docker-compose.yml`:
```yaml
volumes:
- ./bookdrop:/bookdrop
```
---
## Network Storage Mode
For NFS, SMB, or other network-mounted filesystems, set `DISK_TYPE=NETWORK`. This disables destructive UI operations (delete, move, rename) to protect shared mounts while keeping reading, metadata, and sync fully functional.
```ini
# .env
DISK_TYPE=NETWORK
```
---
## Java Backend — Key Patterns
Grimmory is a Java application (Spring Boot + MariaDB). When contributing or extending:
### Project Structure (typical Spring Boot layout)
```
src/main/java/
com/grimmory/
config/ # Spring configuration classes
controller/ # REST API controllers
service/ # Business logic
repository/ # JPA repositories
model/ # JPA entities
dto/ # Data transfer objects
```
### REST API — Base Path
All endpoints are under `/api/v1/`:
```bash
# Health check
GET http://localhost:6060/api/v1/healthcheck
# Books
GET http://localhost:6060/api/v1/books
GET http://localhost:6060/api/v1/books/{id}
POST http://localhost:6060/api/v1/books
PUT http://localhost:6060/api/v1/books/{id}
DELETE http://localhost:6060/api/v1/books/{id}
# Shelves
GET http://localhost:6060/api/v1/shelves
POST http://localhost:6060/api/v1/shelves
# OPDS catalog (for compatible reader apps)
GET http://localhost:6060/opds
```
### Example: Querying the API with Java (OkHttp)
```java
import okhttp3.*;
import com.fasterxml.jackson.databind.ObjectMapper;
public class GrimmoryClient {
private final OkHttpClient http = new OkHttpClient();
private final ObjectMapper mapper = new ObjectMapper();
private final String baseUrl;
private final String token;
public GrimmoryClient(String baseUrl, String token) {
this.baseUrl = baseUrl;
this.token = token;
}
public String getBooks() throws Exception {
Request request = new Request.Builder()
.url(baseUrl + "/api/v1/books")
.header("Authorization", "Bearer " + token)
.build();
try (Response response = http.newCall(request).execute()) {
return response.body().string();
}
}
}
```
### Example: Spring Boot Controller Pattern
```java
@RestController
@RequestMapping("/api/v1/books")
@RequiredArgsConstructor
public class BookController {
private final BookService bookService;
@GetMapping
public ResponseEntity<Page<BookDto>> getAllBooks(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String search) {
return ResponseEntity.ok(bookService.findAll(page, size, search));
}
@GetMapping("/{id}")
public ResponseEntity<BookDto> getBook(@PathVariable Long id) {
return ResponseEntity.ok(bookService.findById(id));
}
@PostMapping
public ResponseEntity<BookDto> createBook(@RequestBody @Valid CreateBookRequest request) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(bookService.create(request));
}
@PutMapping("/{id}/metadata")
public ResponseEntity<BookDto> updateMetadata(
@PathVariable Long id,
@RequestBody @Valid UpdateMetadataRequest request) {
return ResponseEntity.ok(bookService.updateMetadata(id, request));
}
}
```
### Example: JPA Entity Pattern
```java
@Entity
@Table(name = "books")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
private String author;
private String isbn;
private String format; // EPUB, PDF, CBZ, etc.
@Column(name = "file_path")
private String filePath;
@Column(name = "cover_path")
private String coverPath;
@Column(name = "reading_progress")
private Double readingProgress;
@ManyToMany
@JoinTable(
name = "book_shelf",
joinColumns = @JoinColumn(name = "book_id"),
inverseJoinColumns = @JoinColumn(name = "shelf_id")
)
private Set<SheRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.