reason-machines/trending-skills1 file

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.

Specification
Skill ID
reason-machines/trending-skills/grimmory-self-hosted-library
Publisher
reason-machines
Repository
trending-skills
Installs
0
Files
1
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

reason-machines/trending-skills/grimmory-self-hosted-libraryInstalls these files
  • SKILL.md

What this skill tells the agent

Grimmory Self-Hosted Library Manager

Skill by 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

# 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

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

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

VariableDescriptionDefault
USER_IDUID for the app process1000
GROUP_IDGID for the app process1000
TZTimezone stringEtc/UTC
DATABASE_URLJDBC connection stringrequired
DATABASE_USERNAMEDB usernamerequired
DATABASE_PASSWORDDB passwordrequired
DISK_TYPELOCAL or NETWORKLOCAL

Supported Book Formats

CategoryFormats
eBooksEPUB, MOBI, AZW, AZW3
DocumentsPDF
ComicsCBZ, 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:

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.

# .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/:

# 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)

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

@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

@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")
    )

Use this skill in a project

Open any RiverX project, search the Skills panel for reason-machines/trending-skills/grimmory-self-hosted-library, and install it in one click.

Use this skill