migrate: Booklore → Grimmory on new LXC 130
Grimmory (grimmory-tools/grimmory) is the community fork/successor of Booklore. Moving it off the shared apps LXC (105) onto a dedicated container (130, 192.168.8.213) to avoid the Portainer-managed stack footgun that wiped mariadb in April. - containers/130-grimmory.md: new container doc with compose layout, Authentik OIDC notes (PKCE/Public client), media-GID setup - plans/2026-06-29-grimmory-migration.md: step-by-step runbook for DB dump/restore, LXC provisioning, Caddy cutover, Authentik update - inventory.yaml: add grimmory LXC 130 - containers/105-apps.md: remove Booklore section + hostname entry - containers/index.md: update 105 row; add 130 row - infrastructure/media-permissions.md: add LXC 130; update Booklore→Grimmory refs - README.md: update 105 row; add 130 row Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@ See the full table in [`containers/index.md`](containers/index.md). Quick links:
|
||||
| 102 | [nfs-export](containers/102-nfs-export.md) | 192.168.8.200 | NFSv4 re-export of /mnt/library for ZimaOS |
|
||||
| 103 | [paperless](containers/103-paperless.md) | 192.168.8.130 | Document mgmt |
|
||||
| 104 | [gitea](containers/104-gitea.md) | 192.168.8.121 | Git server |
|
||||
| 105 | [apps](containers/105-apps.md) | 192.168.8.205 | Docker host (Artifacto / Booklore / PlantUML / Portainer / WriteFreely) |
|
||||
| 105 | [apps](containers/105-apps.md) | 192.168.8.205 | Docker host (Artifacto / PlantUML / Portainer / WriteFreely) |
|
||||
| 114 | [nextcloud](containers/114-nextcloud.md) | 192.168.8.224 | Personal cloud |
|
||||
| 118 | [elementsynapse](containers/118-elementsynapse.md) | 192.168.8.239 | Matrix Synapse |
|
||||
| 119 | [sophia](containers/119-sophia.md) | 192.168.8.157 | Sophia |
|
||||
@@ -30,6 +30,7 @@ See the full table in [`containers/index.md`](containers/index.md). Quick links:
|
||||
| 121 | [caddy](containers/121-caddy.md) | 192.168.8.175 | Reverse proxy |
|
||||
| 122 | [arriman](containers/122-arriman.md) | 192.168.8.132 | Docker host (\*arr stack) |
|
||||
| 124 | [authentik](containers/124-authentik.md) | 192.168.8.180 | SSO + split-horizon DNS |
|
||||
| 130 | [grimmory](containers/130-grimmory.md) | 192.168.8.213 | Digital library (Grimmory — fork of Booklore) |
|
||||
|
||||
### Cross-cutting infrastructure
|
||||
- [DNS — split-horizon](infrastructure/dns.md)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 105 — `apps`
|
||||
|
||||
Docker host for everything that doesn't justify its own LXC. Currently runs Artifacto, Booklore, PlantUML server, Portainer (and historically WriteFreely / blog), plus the [homelab-context distribution services](../infrastructure/homelab-context.md) (MCP + secrets-issuance) since 2026-05-20.
|
||||
Docker host for everything that doesn't justify its own LXC. Currently runs Artifacto, PlantUML server, Portainer (and historically WriteFreely / blog), plus the [homelab-context distribution services](../infrastructure/homelab-context.md) (MCP + secrets-issuance) since 2026-05-20. Booklore migrated to [grimmory (130)](130-grimmory.md) on 2026-06-29.
|
||||
|
||||
## At a glance
|
||||
- **Hostname:** `apps`
|
||||
@@ -15,7 +15,6 @@ Docker host for everything that doesn't justify its own LXC. Currently runs Arti
|
||||
| Hostname | Container | Backend port | Notes |
|
||||
| --------------------------------- | ---------------- | ------------ | ----- |
|
||||
| `docker.hubris.network` | Portainer | `:9443` | Native OAuth2 via Authentik. Trusted-origins requires hostname only (no scheme/port). |
|
||||
| `books.hubris.network` | Booklore | `:6060` | Native OIDC. Redirect URI `/oauth2-callback`. |
|
||||
| `artifacto.hubris.network` | Artifacto | `:3100` | Public `/p/*`, `/static/*`, `/healthz` exposed via [VPS traefik](../infrastructure/ingress.md). |
|
||||
| `blog.hubris.network` | WriteFreely | `:8080` | Native OIDC via `[oauth.generic]`. |
|
||||
| `git.hubris.network/_plantuml/*` | PlantUML server | `:8079` | Same-origin route from [gitea (104)](104-gitea.md). |
|
||||
@@ -46,11 +45,6 @@ Receiver at `/opt/artifacto-deploy/` (outside the app repo): `deploy.sh` + `webh
|
||||
### Portainer
|
||||
Native OAuth2 (Settings → Authentication → OAuth → Custom). Manual endpoints (no OIDC discovery). Uses `portainer-uid` custom-claim scope from Authentik. Container is **not** compose-managed — safe to `docker run` recreate; data lives in named volume `portainer_data`. CLI flag: `--trusted-origins docker.hubris.network` (hostname only — `IsTrustedOrigin` rejects strings containing `://`).
|
||||
|
||||
### Booklore
|
||||
Native OIDC via Authentik (Settings → OIDC). Redirect URI `/oauth2-callback` (NOT `/api/oidc`). Container needs `extra_hosts: auth.hubris.network:192.168.8.175`. **Edit via Portainer UI** if it's a Portainer-managed stack.
|
||||
|
||||
> ⚠️ **Never `docker compose up` Portainer-managed stacks from the host shell.** Portainer's compose state lives at `/var/lib/docker/volumes/portainer_data/_data/compose/<N>/`. Running `docker compose up -d <svc>` from the host triggers recreates of OTHER services in the stack and silently destroys bind-mounted data. **This wiped Booklore's mariadb data on 2026-04-22.** Use the Portainer UI editor for compose changes. See [mesh migration](../infrastructure/mesh.md#critical-never-docker-compose-up-portainer-managed-stacks) for the full warning.
|
||||
|
||||
### homelab-mcp (`/opt/homelab-mcp/`)
|
||||
FastMCP server (Python venv at `/opt/homelab-mcp/.venv`). Reads from
|
||||
`/opt/homelab-context/` (this LXC is itself an enrolled
|
||||
@@ -113,6 +107,9 @@ Native OIDC via `[oauth.generic]` in `config/config.ini`. `host = https://auth.h
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2026-06-29 — Booklore migrated to Grimmory on LXC 130
|
||||
Booklore stack removed from Portainer. MariaDB dump taken first, then restored into [grimmory (130)](130-grimmory.md)'s fresh MariaDB. `books.hubris.network` Caddy backend updated to `192.168.8.213:6060`. Authentik OIDC provider updated to Public client type (PKCE) for Grimmory compatibility.
|
||||
|
||||
### 2026-05-20 — homelab-mcp + secrets-issuance live
|
||||
Two new services from the [homelab-context distribution plan](../infrastructure/homelab-context.md):
|
||||
`homelab-mcp.service` on `:9810` (MCP read+management surface) and
|
||||
|
||||
66
containers/130-grimmory.md
Normal file
66
containers/130-grimmory.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 130 — `grimmory`
|
||||
|
||||
Self-hosted digital library (eBooks, comics, audiobooks). Community fork/successor of Booklore, with smart shelves, metadata enrichment, Kobo/KOReader sync, OPDS, and a built-in EPUB/PDF reader. Migrated from [apps (105)](105-apps.md) on 2026-06-29.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Hostname:** `grimmory`
|
||||
- **IP:** `192.168.8.213` (static DHCP reservation)
|
||||
- **Privilege:** privileged (UID = host UID for `/mnt/library` media GID)
|
||||
- **Resources:** 1 core / 2 GiB RAM / 16 GiB rootfs (Debian 13)
|
||||
- **Mounts:** `/mnt/library`
|
||||
- **Public hostname:** `books.hubris.network`
|
||||
|
||||
## Service / port map
|
||||
|
||||
| Service | Listen | Notes |
|
||||
|---------|--------|-------|
|
||||
| Grimmory | `192.168.8.213:6060` | Docker Compose at `/opt/grimmory/` |
|
||||
| MariaDB | internal only | Sidecar in the same compose stack |
|
||||
|
||||
## Compose
|
||||
|
||||
Located at `/opt/grimmory/docker-compose.yml`. Key points:
|
||||
|
||||
- Image: `ghcr.io/grimmory-tools/grimmory:latest`
|
||||
- Books library: `/mnt/library/books` → `/books` (read-write; media GID 10000 via `GROUP_ID=10000`)
|
||||
- Bookdrop (watched folder for auto-import): `/opt/grimmory/bookdrop` → `/bookdrop`
|
||||
- App data (covers, DB config): `/opt/grimmory/data` → `/app/data`
|
||||
- MariaDB config: `/opt/grimmory/mariadb/config` → `/config` (linuxserver/mariadb image)
|
||||
- `extra_hosts: auth.hubris.network:192.168.8.175` — routes Authentik OIDC discovery to Caddy from inside the container
|
||||
- `FORCE_DISABLE_OIDC=false` — OIDC stays enabled; provider configured via Grimmory admin UI
|
||||
|
||||
Credentials live in `/opt/grimmory/.env` (untracked):
|
||||
- `DATABASE_PASSWORD` / `MYSQL_PASSWORD` — MariaDB Grimmory user password
|
||||
- `MYSQL_ROOT_PASSWORD` — MariaDB root password
|
||||
|
||||
## Authentik OIDC
|
||||
|
||||
Grimmory uses **PKCE (Public client — no client secret required)**.
|
||||
|
||||
- **Authentik provider:** `Grimmory` (updated from old `Booklore` provider — client type changed to Public)
|
||||
- **Redirect URI:** `https://books.hubris.network/oauth2-callback`
|
||||
- **Scopes:** openid, profile, email, offline_access
|
||||
- **Back-channel logout:** `http://192.168.8.213:6060/api/v1/auth/oidc/backchannel-logout`
|
||||
- **Grimmory admin UI:** Settings → Authentication → OIDC
|
||||
- Issuer URI: `https://auth.hubris.network/application/o/<slug>/` (trailing slash required)
|
||||
- Client ID: from Authentik provider
|
||||
- Test Connection to verify reachability from within the container
|
||||
|
||||
## Media permissions
|
||||
|
||||
LXC is privileged → in-container UID = host UID. Docker container gets media GID via `GROUP_ID=10000` env var (Grimmory/linuxserver pattern). The `/mnt/library/books` subtree is owned `:media` mode `2775` (setgid). See [media-permissions](../infrastructure/media-permissions.md).
|
||||
|
||||
## Related
|
||||
|
||||
- [apps (105)](105-apps.md) — previous host (Booklore)
|
||||
- [Caddy (121)](121-caddy.md) — `books.hubris.network → 192.168.8.213:6060`
|
||||
- [Authentik (124)](124-authentik.md) — OIDC provider `Grimmory`
|
||||
- [DNS (107)](107-dns.md) — `books.hubris.network → 192.168.8.175` (unchanged from Booklore)
|
||||
- [Media permissions](../infrastructure/media-permissions.md)
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2026-06-29 — provisioned; Booklore migrated
|
||||
|
||||
LXC 130 created (Debian 13, privileged, `192.168.8.213`). Docker installed. Grimmory compose deployed at `/opt/grimmory/`. MariaDB dump from Booklore (LXC 105) restored — schema-compatible since Grimmory is a direct fork. Caddy `books.hubris.network` backend updated from `192.168.8.205:6060` to `192.168.8.213:6060`. Authentik provider updated: Booklore → Grimmory, Confidential → Public (PKCE). Booklore stack removed from Portainer on LXC 105.
|
||||
@@ -7,7 +7,7 @@ All containers live on [`hubris`](../hosts/hubris.md). Each row links to the per
|
||||
| 101 | [jellyfin](101-jellyfin.md) | 192.168.8.206 | unpriv (idmap) | 2 | 4 GiB | 16 GiB | `/mnt/library` | `media.hubris.network` | running |
|
||||
| 103 | [paperless](103-paperless.md) | 192.168.8.130 | priv | 2 | 3 GiB | 8 GiB | `/mnt/library` | `paperless.hubris.network` | running |
|
||||
| 104 | [gitea](104-gitea.md) | 192.168.8.121 | priv | 1 | 1 GiB | 8 GiB | `/mnt/library` | `git.hubris.network` | running |
|
||||
| 105 | [apps](105-apps.md) | 192.168.8.205 | priv | 2 | 4 GiB | 30 GiB | `/mnt/library` | `docker` / `books` / `artifacto` / `blog` | running |
|
||||
| 105 | [apps](105-apps.md) | 192.168.8.205 | priv | 2 | 4 GiB | 30 GiB | `/mnt/library` | `docker` / `artifacto` / `blog` | running |
|
||||
| 114 | [nextcloud](114-nextcloud.md) | 192.168.8.224 | priv | 4 | 6 GiB | 25 GiB | `/mnt/library` | `cloud.hubris.network` | running |
|
||||
| 118 | [elementsynapse](118-elementsynapse.md) | 192.168.8.239 | unpriv | 1 | 2 GiB | 8 GiB | — | `matrix.hubris.network` | running |
|
||||
| 119 | [sophia](119-sophia.md) | 192.168.8.157 | priv | 2 | 1 GiB | 10 GiB | `/mnt/library` | — | running |
|
||||
@@ -17,6 +17,7 @@ All containers live on [`hubris`](../hosts/hubris.md). Each row links to the per
|
||||
| 124 | [authentik](124-authentik.md) | 192.168.8.180 | priv | 2 | 4 GiB | 20 GiB | — | `auth.hubris.network` | running |
|
||||
| 128 | [trmnl](128-trmnl.md) | 192.168.8.211 | unpriv | 1 | 768 MiB | 8 GiB | — | `trmnl.hubris.network` | running |
|
||||
| 129 | [house](129-house.md) | 192.168.8.212 | unpriv | 1 | 1344 MiB | 8 GiB | — | `house.hubris.network` | running |
|
||||
| 130 | [grimmory](130-grimmory.md) | 192.168.8.213 | priv | 1 | 2 GiB | 16 GiB | `/mnt/library` | `books.hubris.network` | running |
|
||||
|
||||
## Recently destroyed (kept for archaeology)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ Every LXC that mounts `/mnt/library` participates in a shared `media` group with
|
||||
- `/etc/subgid` has `root:100000:65536` AND `root:10000:1` (second line required for unprivileged LXCs to receive GID 10000).
|
||||
- Shared subtrees owned `:media` mode `2775` (drwxrwsr-x, setgid):
|
||||
- `movies`, `tv`, `music`, `anime`, `podcasts` — jellyfin libraries
|
||||
- `audiobooks`, `audiobookshelf-metadata`, `books`, `comics` — audiobookshelf / booklore
|
||||
- `audiobooks`, `audiobookshelf-metadata`, `books`, `comics` — audiobookshelf / grimmory
|
||||
- `downloads` — \*arr stack output
|
||||
- `images` — photoprism / immich / mulita
|
||||
- `roms` — emu frontends
|
||||
@@ -61,6 +61,7 @@ Every LXC that mounts `/mnt/library` participates in a shared `media` group with
|
||||
| 119 | [sophia](../containers/119-sophia.md) | priv | www-data |
|
||||
| 120 | [mule-images](../containers/120-mule-images.md) | priv | www-data |
|
||||
| 122 | [arriman](../containers/122-arriman.md) | priv | www-data, audiobookshelf, radarr, sonarr, lidarr, prowlarr, qbittorrent, bazarr, jellyseerr, mylar, jackett, overseerr, plex, arr |
|
||||
| 130 | [grimmory](../containers/130-grimmory.md) | priv | Docker container uses `GROUP_ID=10000` env var (linuxserver pattern) — no in-LXC group needed |
|
||||
|
||||
> Some entries from earlier snapshots — 100 (arr-yunohost), 107 (marimo), 109 (syncthing), 110 (photoprism), 112 (immich), 116 (heaper) — referenced LXCs that have since been destroyed. See [containers/index](../containers/index.md#recently-destroyed-kept-for-archaeology).
|
||||
|
||||
@@ -68,7 +69,7 @@ Config backups: `/root/101.conf.bak.*`, `/root/109.conf.bak.*` (109 destroyed 20
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **[apps (105)](../containers/105-apps.md) is a Docker host.** Adding `media` to the LXC alone is *not* enough for Docker containers inside. Each Docker container needs its GID passed in explicitly: `--group-add 10000` or `user: "<uid>:10000"` in compose. Booklore, audiobookshelf-in-docker, etc. need this per-container.
|
||||
- **[apps (105)](../containers/105-apps.md) and [grimmory (130)](../containers/130-grimmory.md) are Docker hosts.** Adding `media` to the LXC alone is *not* enough for Docker containers inside. Each Docker container needs its GID passed in explicitly: `--group-add 10000`, `user: "<uid>:10000"`, or `GROUP_ID=10000` (linuxserver images) in compose. Grimmory, audiobookshelf-in-docker, etc. need this per-container.
|
||||
- **`pct exec` does NOT run initgroups.** So `pct exec <id> -- id` shows only the primary group. For interactive verification, use `pct exec <id> -- sudo -i -u root id` or `su - <user> -c id`. Real systemd services work fine.
|
||||
- **systemd `User=root`** skips initgroups — explicit `SupplementaryGroups=media` drop-in needed.
|
||||
- **`pct restore`** or template rebuilds wipe in-container group membership and unprivileged-LXC idmap blocks. Re-apply from this page.
|
||||
|
||||
@@ -211,6 +211,7 @@ hosts:
|
||||
- plantuml
|
||||
- homelab-mcp
|
||||
- secrets-issuance
|
||||
# booklore removed 2026-06-29 → migrated to grimmory (LXC 130)
|
||||
age_pubkey: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0
|
||||
auth-outpost:
|
||||
kind: lxc
|
||||
@@ -313,6 +314,20 @@ hosts:
|
||||
mounts:
|
||||
- /mnt/library
|
||||
age_pubkey: ''
|
||||
grimmory:
|
||||
kind: lxc
|
||||
pve_id: 130
|
||||
host: hubris
|
||||
os: linux
|
||||
role: book-library
|
||||
lan_ip: 192.168.8.213
|
||||
public_host: books.hubris.network
|
||||
mounts:
|
||||
- /mnt/library
|
||||
notes:
|
||||
- Docker host for Grimmory (community fork of Booklore). Created 2026-06-29.
|
||||
- Migrated from apps LXC 105; MariaDB data carried over (schema-compatible fork).
|
||||
age_pubkey: '' # filled by homelab client add --finalize-pubkey
|
||||
# 123 (claudio-bot) — destroyed 2026-06-04, replaced by Hermes Agent
|
||||
# 126 (plato) — destroyed 2026-06-28, notes workspace decommissioned
|
||||
zimaos:
|
||||
|
||||
246
plans/2026-06-29-grimmory-migration.md
Normal file
246
plans/2026-06-29-grimmory-migration.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# Plan: Migrate Booklore → Grimmory (LXC 130)
|
||||
|
||||
**Status:** in-progress
|
||||
**Date:** 2026-06-29
|
||||
**Goal:** Replace Booklore on shared apps LXC 105 with Grimmory on a dedicated LXC 130. Grimmory is the community fork/successor of Booklore with the same database schema and port, so the migration is a near-drop-in swap.
|
||||
|
||||
---
|
||||
|
||||
## Pre-flight checklist
|
||||
|
||||
- [ ] Note Booklore MariaDB credentials from Portainer compose on LXC 105 (`DATABASE_PASSWORD`, `MYSQL_ROOT_PASSWORD`)
|
||||
- [ ] Confirm `/mnt/library/books` is readable on LXC 105 (`ls /mnt/library/books | head`)
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Dump Booklore MariaDB
|
||||
|
||||
```bash
|
||||
# On hubris — find the MariaDB container name
|
||||
pct exec 105 -- docker ps --format '{{.Names}}' | grep -i maria
|
||||
|
||||
# Dump (replace <CONTAINER> and <PASSWORD> from Portainer compose)
|
||||
pct exec 105 -- docker exec <CONTAINER> \
|
||||
mysqldump -u grimmory -p<PASSWORD> grimmory \
|
||||
> /tmp/booklore-$(date +%Y%m%d).sql
|
||||
|
||||
# Pull to hubris root for safekeeping
|
||||
pct pull 105 /tmp/booklore-$(date +%Y%m%d).sql /root/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Create LXC 130
|
||||
|
||||
```bash
|
||||
# On hubris — list available Debian 13 templates
|
||||
pveam list local | grep debian-13
|
||||
|
||||
# Create LXC
|
||||
pct create 130 local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst \
|
||||
--hostname grimmory \
|
||||
--ostype debian \
|
||||
--unprivileged 0 \
|
||||
--cores 1 --memory 2048 --rootfs local-lvm:16 \
|
||||
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
|
||||
--onboot 1 \
|
||||
--mp0 /mnt/library,mp=/mnt/library \
|
||||
--features nesting=1
|
||||
|
||||
pct start 130
|
||||
pct exec 130 -- apt-get update -qq
|
||||
```
|
||||
|
||||
Get the MAC address for the DHCP reservation:
|
||||
```bash
|
||||
pct config 130 | grep hwaddr
|
||||
```
|
||||
→ Add static DHCP lease `192.168.8.213` for this MAC on the Fritz!Box.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Bootstrap LXC 130
|
||||
|
||||
```bash
|
||||
pct exec 130 -- bash -c '
|
||||
# Media group
|
||||
groupadd -g 10000 media
|
||||
|
||||
# Docker
|
||||
apt-get install -y ca-certificates curl
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
systemctl enable --now docker
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Deploy Grimmory compose
|
||||
|
||||
```bash
|
||||
pct exec 130 -- mkdir -p /opt/grimmory/mariadb/config /opt/grimmory/data /opt/grimmory/bookdrop
|
||||
```
|
||||
|
||||
Write `/opt/grimmory/docker-compose.yml` on LXC 130:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
grimmory:
|
||||
image: ghcr.io/grimmory-tools/grimmory:latest
|
||||
container_name: grimmory
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "192.168.8.213:6060:6060"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- /mnt/library/books:/books
|
||||
- ./bookdrop:/bookdrop
|
||||
environment:
|
||||
- DATABASE_URL=jdbc:mariadb://mariadb:3306/grimmory
|
||||
- DATABASE_USERNAME=grimmory
|
||||
- DATABASE_PASSWORD=${GRIMMORY_DB_PASSWORD}
|
||||
- USER_ID=0
|
||||
- GROUP_ID=10000
|
||||
- TZ=Europe/Berlin
|
||||
- FORCE_DISABLE_OIDC=false
|
||||
extra_hosts:
|
||||
- "auth.hubris.network:192.168.8.175"
|
||||
depends_on:
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
|
||||
mariadb:
|
||||
image: lscr.io/linuxserver/mariadb:11.4.8
|
||||
container_name: grimmory-mariadb
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./mariadb/config:/config
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
||||
- MYSQL_DATABASE=grimmory
|
||||
- MYSQL_USER=grimmory
|
||||
- MYSQL_PASSWORD=${GRIMMORY_DB_PASSWORD}
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
```
|
||||
|
||||
Write `/opt/grimmory/.env` on LXC 130 (fill real passwords):
|
||||
```
|
||||
GRIMMORY_DB_PASSWORD=<same_password_as_booklore>
|
||||
MYSQL_ROOT_PASSWORD=<root_password>
|
||||
```
|
||||
|
||||
Start:
|
||||
```bash
|
||||
pct exec 130 -- bash -c 'cd /opt/grimmory && docker compose up -d mariadb'
|
||||
# wait ~15s for MariaDB to init, then start grimmory
|
||||
pct exec 130 -- bash -c 'cd /opt/grimmory && docker compose up -d'
|
||||
```
|
||||
|
||||
Verify Grimmory responds (before DB restore — will show setup wizard):
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}' http://192.168.8.213:6060
|
||||
# expect 200 or 302
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Restore Booklore DB
|
||||
|
||||
```bash
|
||||
# Stop Grimmory (keep MariaDB running)
|
||||
pct exec 130 -- docker stop grimmory
|
||||
|
||||
# Copy dump to LXC 130
|
||||
pct push 130 /root/booklore-$(date +%Y%m%d).sql /tmp/booklore.sql
|
||||
|
||||
# Restore (replace <PASSWORD>)
|
||||
pct exec 130 -- docker exec -i grimmory-mariadb \
|
||||
mysql -u grimmory -p<GRIMMORY_DB_PASSWORD> grimmory \
|
||||
< /tmp/booklore.sql
|
||||
|
||||
# Restart Grimmory
|
||||
pct exec 130 -- docker start grimmory
|
||||
```
|
||||
|
||||
Verify books appear:
|
||||
```bash
|
||||
curl -s http://192.168.8.213:6060 | grep -i grimmory
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — Authentik OIDC update
|
||||
|
||||
In Authentik Admin UI (`https://auth.hubris.network`):
|
||||
|
||||
1. Providers → find `Booklore` provider
|
||||
2. Edit:
|
||||
- Name: `Grimmory`
|
||||
- Client Type: **Public** (Grimmory uses PKCE — no secret needed)
|
||||
- Redirect URIs: `https://books.hubris.network/oauth2-callback`
|
||||
- Scopes: openid, profile, email, offline_access
|
||||
- Back-channel logout URL: `http://192.168.8.213:6060/api/v1/auth/oidc/backchannel-logout`
|
||||
3. Note the **Client ID** and **Application slug** for Grimmory's OIDC settings
|
||||
|
||||
In Grimmory Admin UI (`http://192.168.8.213:6060` → Settings → Authentication → OIDC):
|
||||
- Issuer URI: `https://auth.hubris.network/application/o/<slug>/` (trailing slash required!)
|
||||
- Client ID: (from Authentik)
|
||||
- Client Secret: leave blank (PKCE)
|
||||
- Click **Test Connection** — all checks should pass (container reaches Authentik via extra_hosts)
|
||||
|
||||
---
|
||||
|
||||
## Step 7 — Caddy cutover
|
||||
|
||||
In the `dtoro/caddy-conf` repo, update `books.hubris.network`:
|
||||
|
||||
```caddy
|
||||
books.hubris.network {
|
||||
reverse_proxy 192.168.8.213:6060
|
||||
}
|
||||
```
|
||||
|
||||
Git push → Caddy webhook auto-reloads (see [caddy (121)](../containers/121-caddy.md)).
|
||||
|
||||
Test:
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://books.hubris.network
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 8 — Enroll as homelab client
|
||||
|
||||
```bash
|
||||
homelab client add grimmory --lan-ip 192.168.8.213
|
||||
```
|
||||
|
||||
Commits the `age_pubkey` back to `inventory.yaml`.
|
||||
|
||||
---
|
||||
|
||||
## Step 9 — Verify end-to-end
|
||||
|
||||
- [ ] `https://books.hubris.network` loads Grimmory
|
||||
- [ ] OIDC login via Authentik works
|
||||
- [ ] Library books from `/mnt/library/books` are visible
|
||||
- [ ] Reading progress / metadata from Booklore is present
|
||||
|
||||
---
|
||||
|
||||
## Step 10 — Decommission Booklore on LXC 105
|
||||
|
||||
1. Portainer → navigate to the Booklore stack → Stop → Remove
|
||||
2. Keep the dump at `/root/booklore-<date>.sql` on hubris (or archive to `/mnt/library/documents/`)
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
If something goes wrong before Caddy cutover: no user-visible impact, just shut down LXC 130.
|
||||
|
||||
If Caddy already cut over: revert the `books.hubris.network` block to `192.168.8.205:6060` and push. Booklore still running on LXC 105 until Portainer stack is removed.
|
||||
Reference in New Issue
Block a user