grimmory-self-hosted-library

安装量: 1.1K
排名: #3984

安装

npx skills add https://github.com/aradotso/trending-skills --skill grimmory-self-hosted-library

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 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: Watch — Grimmory monitors /bookdrop continuously Detect — New files are picked up and parsed Enrich — Metadata fetched from Google Books / Open Library 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"
)
)
private
Set
<
Shelf
>
shelves
=
new
HashSet
<
>
(
)
;
@CreationTimestamp
private
LocalDateTime
createdAt
;
@UpdateTimestamp
private
LocalDateTime
updatedAt
;
}
Example: Service with Metadata Enrichment
@Service
@RequiredArgsConstructor
public
class
MetadataService
{
private
final
GoogleBooksClient
googleBooksClient
;
private
final
OpenLibraryClient
openLibraryClient
;
private
final
BookRepository
bookRepository
;
public
BookDto
enrichMetadata
(
Long
bookId
)
{
Book
book
=
bookRepository
.
findById
(
bookId
)
.
orElseThrow
(
(
)
->
new
BookNotFoundException
(
bookId
)
)
;
// Try Google Books first
Optional
<
BookMetadata
>
metadata
=
googleBooksClient
.
search
(
book
.
getTitle
(
)
,
book
.
getAuthor
(
)
)
;
// Fall back to Open Library
if
(
metadata
.
isEmpty
(
)
)
{
metadata
=
openLibraryClient
.
search
(
book
.
getIsbn
(
)
)
;
}
metadata
.
ifPresent
(
m
->
{
book
.
setDescription
(
m
.
getDescription
(
)
)
;
book
.
setCoverUrl
(
m
.
getCoverUrl
(
)
)
;
book
.
setPublisher
(
m
.
getPublisher
(
)
)
;
book
.
setPublishedDate
(
m
.
getPublishedDate
(
)
)
;
bookRepository
.
save
(
book
)
;
}
)
;
return
BookDto
.
from
(
book
)
;
}
}
OPDS Integration
Connect any OPDS-compatible reader app (Kybook, Chunky, Moon+ Reader, etc.) using:
http://:6060/opds
Authenticate with your Grimmory username and password when prompted.
Kobo / KOReader Sync
Kobo
Connect via the device sync feature in Grimmory settings. The app exposes a sync endpoint compatible with Kobo's API.
KOReader
Configure KOReader's sync plugin to point to your Grimmory instance URL. Multi-User & Authentication Local Authentication Create users from the admin panel at http://localhost:6060 . Each user has isolated shelves, reading progress, and preferences. OIDC Authentication Configure via environment variables (refer to full documentation at https://grimmory.org/docs/getting-started for OIDC-specific variables such as OIDC_ISSUER_URI , OIDC_CLIENT_ID , OIDC_CLIENT_SECRET ). Building from Source

Clone the repository

git clone https://github.com/grimmory-tools/grimmory.git cd grimmory

Build with Maven

./mvnw clean package -DskipTests

Or build Docker image locally

docker build -t grimmory:local .

Use local build in docker-compose.yml

Comment out 'image' and uncomment 'build: .'

Common Docker Commands

Start services

docker compose up -d

Stop services

docker compose down

View app logs

docker compose logs -f grimmory

View DB logs

docker compose logs -f mariadb

Restart only the app

docker compose restart grimmory

Pull latest image and redeploy

docker compose pull && docker compose up -d

Open a shell inside the container

docker exec -it grimmory /bin/bash

Database shell

docker exec -it mariadb mariadb -u grimmory -p grimmory Troubleshooting Container won't start — DB connection refused

Check MariaDB health

docker compose ps mariadb

Should show "healthy". If not:

docker compose logs mariadb

Ensure DATABASE_URL host matches the service name: mariadb:3306

Books not appearing after BookDrop

Verify file permissions — UID/GID must match APP_USER_ID/APP_GROUP_ID

ls -la ./bookdrop/

Check app logs for detection events

docker compose logs -f grimmory | grep -i bookdrop Permission denied on ./books or ./data

Set ownership to match APP_USER_ID / APP_GROUP_ID

sudo chown -R 1000 :1000 ./books ./data ./bookdrop OPDS not accessible from reader app

Confirm port 6060 is reachable from your device

curl http:// < host-ip

:6060/api/v1/healthcheck

Check firewall rules if on a remote server

High memory usage MariaDB and Grimmory together require at minimum ~512 MB RAM. For large libraries (10k+ books), allocate 1–2 GB. Metadata not enriching Google Books and Open Library require outbound internet access from the container. Verify DNS and network: docker exec -it grimmory curl -s "https://www.googleapis.com/books/v1/volumes?q=test" Contributing Before opening a pull request: Open an issue and get maintainer approval Include screenshots/video proof and pasted test output Follow backend and frontend conventions in CONTRIBUTING.md AI-assisted code is allowed but you must run, test, and understand every line

Run tests before submitting

./mvnw test

Check code style

./mvnw checkstyle:check
Links
GitHub
:
https://github.com/grimmory-tools/grimmory
Docker Hub
:
https://hub.docker.com/r/grimmory/grimmory
GHCR
:
ghcr.io/grimmory-tools/grimmory
Discord
:
https://discord.gg/FwqHeFWk
Docs
:
https://grimmory.org/docs/getting-started
License
AGPL-3.0
返回排行榜