# LazyLibrarian

**🟢 Media Service**

## 📋 Service Overview

| Property | Value |
|----------|-------|
| **Service Name** | lazylibrarian |
| **Host** | Atlantis (Synology) |
| **Category** | Media / Books |
| **Difficulty** | 🟡 |
| **Docker Image** | `lscr.io/linuxserver/lazylibrarian:latest` |
| **Compose File** | `hosts/synology/atlantis/arr-suite/docker-compose.yml` |
| **Directory** | `hosts/synology/atlantis/arr-suite` |

## 🎯 Purpose

LazyLibrarian is an ebook and audiobook download automation tool, similar to Sonarr/Radarr but for books. It monitors authors you follow, searches indexers for new releases, and automatically downloads them via SABnzbd or torrent clients. This is the replacement for the retired Readarr project.

## 🚀 Quick Start

### Prerequisites
- Docker and Docker Compose installed
- Download client configured (SABnzbd and/or Deluge)
- Indexer access (via Prowlarr or direct)
- Access to the host system (Atlantis)

### Deployment
```bash
# Navigate to service directory
cd hosts/synology/atlantis/arr-suite

# Start the service
docker-compose -f docker-compose.yml up -d lazylibrarian

# Check service status
docker-compose -f docker-compose.yml ps

# View logs
docker-compose -f docker-compose.yml logs -f lazylibrarian
```

## 🔧 Configuration

### Docker Compose Configuration
```yaml
lazylibrarian:
  image: lscr.io/linuxserver/lazylibrarian:latest
  container_name: lazylibrarian
  environment:
    - PUID=1029
    - PGID=100
    - TZ=America/Los_Angeles
    - UMASK=022
    - DOCKER_MODS=ghcr.io/themepark-dev/theme.park:lazylibrarian|ghcr.io/linuxserver/mods:lazylibrarian-calibre
    - TP_SCHEME=http
    - TP_DOMAIN=192.168.0.200:8580
    - TP_THEME=dracula
  volumes:
    - /volume2/metadata/docker2/lazylibrarian:/config
    - /volume1/data:/data
    - /volume3/usenet:/sab
    - /volume2/torrents:/downloads  # Deluge download dir
    - /volume2/metadata/docker2/lazylibrarian-scripts/custom-cont-init.d:/custom-cont-init.d
  ports:
    - "5299:5299"
  networks:
    media2_net:
      ipv4_address: 172.24.0.5
  security_opt:
    - no-new-privileges:true
  restart: always
```

### Environment Variables
| Variable | Value | Description |
|----------|-------|-------------|
| `PUID` | `1029` | User ID for file permissions |
| `PGID` | `100` | Group ID for file permissions |
| `TZ` | `America/Los_Angeles` | Timezone setting |
| `UMASK` | `022` | File permission mask |
| `DOCKER_MODS` | `...theme.park:lazylibrarian\|...mods:lazylibrarian-calibre` | Theme.park + Calibre (ebook-convert) |
| `TP_SCHEME` | `http` | Theme.park scheme |
| `TP_DOMAIN` | `192.168.0.200:8580` | Theme.park host |
| `TP_THEME` | `dracula` | Theme selection |

### Port Mappings
| Host Port | Container Port | Protocol | Purpose |
|-----------|----------------|----------|----------|
| 5299 | 5299 | TCP | Web UI |

### Volume Mappings
| Host Path | Container Path | Type | Purpose |
|-----------|----------------|------|----------|
| `/volume2/metadata/docker2/lazylibrarian` | `/config` | bind | Configuration files |
| `/volume1/data` | `/data` | bind | Media library root |
| `/volume3/usenet` | `/sab` | bind | Download directory |

## 🌐 Access Information

| Interface | URL |
|-----------|-----|
| Web UI | `http://192.168.0.200:5299` |

## 🔧 Initial Setup

### 1. Download Clients

**SABnzbd Configuration:**
| Setting | Value |
|---------|-------|
| Host | `192.168.0.200` |
| Port | `8080` |
| API Key | (from SABnzbd → Config → General) |
| Category | `books` |

**Deluge Configuration (via Gluetun):**
| Setting | Value |
|---------|-------|
| Host | `172.24.0.20` |
| Port | `8112` |
| Password | (your deluge password) |

### 2. Providers (Indexers)

**Using Prowlarr (Recommended):**
| Setting | Value |
|---------|-------|
| Host | `172.24.0.6` |
| Port | `9696` |
| API Key | (from Prowlarr → Settings → General) |

### 3. Processing Paths
| Setting | Value |
|---------|-------|
| eBook Library Folder | `/data/media/ebooks` |
| AudioBook Library Folder | `/data/media/audiobooks` |
| Download Complete Folder | `/sab/complete` |

## 📖 Adding Books

### Via the Web UI
1. **Authors → Add Author** → search by name → set to **Active** to track all their books
2. **Books** → find the book → click **Wanted** to queue it for download
3. LL searches indexers automatically on schedule (every 6h) or trigger manually: **Tools → Search Wanted**

### API Access

| Field | Value |
|-------|-------|
| **URL** | http://192.168.0.200:5299 |
| **API Key** | `REDACTED_LL_API_KEY` |
| **Config file** | `/volume2/metadata/docker2/lazylibrarian/config.ini` (on Atlantis) |

Useful read/write CFG shortcuts:
```bash
# Read a config value
curl "http://192.168.0.200:5299/api?apikey=REDACTED_LL_API_KEY&cmd=readCFG&name=<NAME>&group=<Group>"

# Write a config value
curl "http://192.168.0.200:5299/api?apikey=REDACTED_LL_API_KEY&cmd=writeCFG&name=<NAME>&group=<Group>&value=<val>"
```

### Via API
```bash
LL_API="http://192.168.0.200:5299/api?apikey=REDACTED_LL_API_KEY"

# 1. Find the book — returns bookid and authorid
curl "$LL_API&cmd=findBook&name=Book+Title+Author+Name"

# 2. Add it to the database
curl "$LL_API&cmd=addBook&id=<bookid>&wait=1"

# 3. Mark as Wanted (type=eBook or type=AudioBook)
curl "$LL_API&cmd=queueBook&id=<bookid>&type=eBook"

# 4. Trigger immediate search (don't wait 6h)
curl "$LL_API&cmd=forceBookSearch&type=eBook"
```

### Language filter warning
LL may log `Language [Unknown] does not match preference` for some books — this is a **warning only** and does not block the download. The book will still be grabbed.

### Flow after download
SABnzbd downloads → LL post-processor imports to library folder → Audiobookshelf watcher detects → available in ABS app.

## 🔒 Security Considerations

- ✅ Security options configured (no-new-privileges)
- ✅ Running with specific user/group IDs
- ✅ Theme.park integration for consistent UI

## 📊 Resource Requirements

### Recommended Resources
- **Minimum RAM**: 256MB
- **Recommended RAM**: 512MB
- **CPU**: 1 core minimum
- **Storage**: Varies by library size

### Resource Monitoring
```bash
docker stats lazylibrarian
```

## 🚨 Troubleshooting

### Common Issues

**Prowlarr reports "Applications unavailable due to failures" for LazyLibrarian**
- **Root cause:** LazyLibrarian's API is disabled by default. Prowlarr requires the API to sync indexers.
- **Fix:** In LazyLibrarian → Config → General, enable the API and save. Copy the API key.
  In Prowlarr → Settings → Apps → LazyLibrarian, ensure the API key matches, then click Test.
- **Error seen in Prowlarr logs:** `LazyLibrarianException: LazyLibrarian Error - Code 501: API not enabled`

**Searches complete instantly with "found 0 books"**
- **Root cause:** Newznab/Torznab providers are disabled by default (`ENABLED = False` in source). The
  config.ini must have `enabled = true` explicitly in each provider section.
- **Fix:** Stop the container, add `enabled = true` as the first key under each `[Newznab_N]` and
  `[Torznab_N]` section in `/config/config.ini`, then restart.
  ```bash
  docker stop lazylibrarian
  python3 /tmp/fix_ll_config.py   # see script below
  docker start lazylibrarian
  ```
  Script (`/tmp/fix_ll_config.py`):
  ```python
  import re
  path = "/volume2/metadata/docker2/lazylibrarian/config.ini"
  with open(path) as f: lines = f.readlines()
  out = []
  for i, line in enumerate(lines):
      out.append(line)
      if re.match(r"^\[(Newznab|Torznab)_\d+\]\s*$", line):
          j = i + 1
          if not any(re.match(r"^enabled\s*=", l, re.I) for l in lines[j:j+10] if not l.startswith("[")):
              out.append("enabled = true\n")
  with open(path, "w") as f: f.writelines(out)
  ```

**SABnzbd download stuck in "Grabbing" forever / Torrent grab fails with `[Errno 2]`**
- **Root cause:** Newznab/Torznab provider hosts use the Docker container name (`prowlarr:9696`).
  SABnzbd runs on the host network and can't resolve it; torrent grabs silently fail when LL can't
  reach the Prowlarr download URL, leaving it trying to open the result title as a file path.
- **Fix:** Stop the container, replace `prowlarr:9696` with `192.168.0.200:9696` in ALL provider
  `host` entries (Newznab and Torznab), then restart.
  ```bash
  # Via Portainer API (container stop/start):
  curl -sk -X POST -H "X-API-Key: <portainer-token>" \
    "https://192.168.0.200:9443/api/endpoints/2/docker/containers/<id>/stop"
  ssh atlantis "sed -i 's|http://prowlarr:9696/|http://192.168.0.200:9696/|g' \
    /volume2/metadata/docker2/lazylibrarian/config.ini"
  curl -sk -X POST -H "X-API-Key: <portainer-token>" \
    "https://192.168.0.200:9443/api/endpoints/2/docker/containers/<id>/start" -H "Content-Type: application/json" -d '{}'
  ```

**SABnzbd not configured / "No NZB download method is enabled"**
- **Root cause:** SABnzbd connection not set up in LazyLibrarian.
- **Fix:** Use the writeCFG API (or Config → Downloaders → SABnzbd in the UI):
  ```bash
  LL_API="http://192.168.0.200:5299/api?apikey=<your-key>"
  curl "$LL_API&cmd=writeCFG&name=SAB_HOST&group=SABnzbd&value=192.168.0.200"
  curl "$LL_API&cmd=writeCFG&name=SAB_PORT&group=SABnzbd&value=8080"
  curl "$LL_API&cmd=writeCFG&name=SAB_API&group=SABnzbd&value=<sab-api-key>"
  curl "$LL_API&cmd=writeCFG&name=SAB_CAT&group=SABnzbd&value=books"
  curl "$LL_API&cmd=writeCFG&name=NZB_DOWNLOADER_SABNZBD&group=USENET&value=1"
  ```
  SABnzbd API key: `docker exec sabnzbd grep api_key /config/sabnzbd.ini`

**eBook library path not set / books import to /config**
- **Root cause:** `EBOOK_DIR` defaults to empty, so imported books land in `/config`.
- **Fix:** Set the library path:
  ```bash
  curl "http://192.168.0.200:5299/api?cmd=writeCFG&name=EBOOK_DIR&group=General&value=/data/media/ebooks&apikey=<key>"
  ```
  Or in UI: Config → Processing → eBook Library Folder → `/data/media/ebooks`

**AudioBook library path not set / audiobooks import to /config**
- **Root cause:** `AUDIO_DIR` defaults to empty, so imported audiobooks land in `/config` instead of the Audiobookshelf-watched directory.
- **Fix:** Set the library path:
  ```bash
  curl "http://192.168.0.200:5299/api?cmd=writeCFG&name=AUDIO_DIR&group=General&value=/data/media/audiobooks&apikey=<key>"
  ```
  Or in UI: Config → Processing → AudioBook Library Folder → `/data/media/audiobooks`
  Verify: `curl "http://192.168.0.200:5299/api?apikey=<key>&cmd=readCFG&name=AUDIO_DIR&group=General"`

**Torrent download fails with `[Errno 2] No such file or directory: '<result title>'`**
- **Root cause:** LL's `deluge.py` identifies valid .torrent data by checking for `b'announce'` in the
  first 40 bytes. Tracker-less torrents (generated from magnet links by clients like go.torrent) start
  with `d7:comment` instead, causing LL to fall back to treating the result title as a local file path.
- **Fix:** A custom-cont-init.d script patches `deluge.py` on startup to also accept bencoded dicts
  (any data starting with `d`, which all valid .torrent files do). The patch and compose mount are
  already applied. Script at:
  `/volume2/metadata/docker2/lazylibrarian-scripts/custom-cont-init.d/99-patch-deluge.sh`

**SAB_DIRECTORY cannot be set via writeCFG API**
- **Root cause:** `writeCFG` returns OK but `SAB_DIRECTORY` is not written to `config.ini` by some LL
  versions (possible key mismatch). LL won't know where SABnzbd puts completed downloads and won't
  auto-import them.
- **Workaround:** Manually trigger post-processing after a download completes:
  ```bash
  curl "http://192.168.0.200:5299/api?apikey=REDACTED_LL_API_KEY&cmd=forceProcess&dir=/sab/complete/books"
  ```
  This tells LL to scan the SABnzbd books category output dir and import any completed files.

**KEEP_SEEDING setting does not persist**
- **Root cause:** `writeCFG` returns OK for `KEEP_SEEDING` but the value is not actually saved.
  Manually editing `config.ini` also fails — LL reads config into memory at startup and writes it back
  on shutdown, overwriting manual edits made while the container is running.
- **Workaround:** None found. The setting must be changed through the LL web UI
  (Config → Processing → Keep seeding after import), which writes it to the in-memory config.

**NZB audiobook downloads deliver ebooks (epub/mobi) instead of audio files (m4b/mp3)**
- **Root cause:** Some Prowlarr indexers (especially 1337x) classify audio releases under NZB
  categories. LL searches both NZB and Torznab for AudioBooks. An indexer may have an epub labeled
  as an audiobook at a high match percentage, and LL grabs it. The epub lands in the audiobook
  directory and is ignored by Audiobookshelf.
- **Fix:** If an audiobook download is an epub, check `docker logs lazylibrarian` for the grabbed
  URL, then mark the book as Wanted again and re-search. If the NZB result keeps winning, you may
  need to add the specific provider to a blocklist or manually grab a torrent via the UI.

**Downloads not starting**
- Verify download client connection in Config → Downloaders
- Check API keys are correct
- Ensure indexers are configured and working

**Books not importing**
- Check file permissions (PUID/PGID)
- Verify library paths are correct
- Manually trigger PostProcessor: `curl "$LL_API&cmd=forceProcess&dir=/sab/complete/books"`

**Metadata not found**
- Try different metadata providers (Google Books, OpenLibrary, etc.)
- Search by ISBN if available
- Manual metadata entry

### Useful Commands
```bash
# View real-time logs
docker logs -f lazylibrarian

# Restart service
docker restart lazylibrarian

# Update service
docker pull lscr.io/linuxserver/lazylibrarian:latest
docker restart lazylibrarian

# Access service shell
docker exec -it lazylibrarian /bin/bash
```

## 📚 Additional Resources

- **Official Documentation**: [LazyLibrarian Wiki](https://lazylibrarian.gitlab.io/)
- **GitLab**: [LazyLibrarian/LazyLibrarian](https://gitlab.com/LazyLibrarian/LazyLibrarian)
- **LinuxServer.io**: [Docker Image Docs](https://docs.linuxserver.io/images/docker-lazylibrarian)

## 🔗 Related Services

Services REDACTED_APP_PASSWORD LazyLibrarian:
- Audiobookshelf (playback/serving)
- Calibre-Web (ebook management)
- SABnzbd (usenet downloads)
- Deluge (torrent downloads)
- Prowlarr (indexer management)

---

*Last Updated*: 2026-03-02 (Calibre mod, torrent patch, troubleshooting updates)
*Configuration Source*: `hosts/synology/atlantis/arr-suite/docker-compose.yml`
