Self-hosted comic & graphic novel release browser, download manager, and local library reader
Overview β’ Features β’ Quick Start β’ Desktop App β’ Development β’ API β’ Contributing
PanelShelf aggregates comic book and graphic novel releases from multiple online sources into a single, searchable, filterable catalog β then helps you download and read them. It's a self-hosted alternative to commercial comic trackers, built for collectors who want to curate their own digital library.
πΌοΈ Screenshots coming soon
Sections
PanelShelf solves a common problem for comic collectors: releases are scattered across dozens of sites (GetComics, Digital Comic Museum, Internet Archive, ZipComic, and hundreds of RSS/WordPress feeds). Manually checking each one is tedious.
- Aggregate β Add any comic release site as a "source" and PanelShelf indexes its catalog
- Search β Search across your entire indexed catalog or do live searches against source sites in real-time
- Download β Track download links, resolve redirect chains, and manage your download queue
- Read β Import your local CBZ/CBR files and read them in the built-in comic reader
- Desktop app β Optional Tauri v2 native wrapper with sidecar backend
Status: Active development. The core features are functional, but APIs and storage format may change before v1.0.
- Pluggable provider system β Each comic source site gets a provider adapter that knows how to scrape it
- Source types: RSS/Atom feeds, WordPress sites, Digital Comic Museum, ZipComic (with Cloudflare bypass), Internet Archive, GetComics, and more
- Auto-detection β Paste a URL and PanelShelf detects the source type automatically
- Per-source refresh intervals with rate limiting
- Dual catalog β Persisted SQLite index (fast offline queries) + live search results from source sites
- Indexed catalog β All ingested items stored in SQLite with full filtering and sorting
- Live search β Real-time search across source feeds and provider sites with progressive loading (stale-while-revalidate β cached results appear instantly while fresh data loads)
- Infinite scroll β Smooth progressive pagination with configurable trigger distance
- Metadata extraction β Automatically extracts cover art, publisher, format, file size, release date, series, issue number, and download links
- Grid & table views β Toggle between visual cover grid and compact data table
- Filters: publisher, series, format, language, source, download availability, date ranges
- Sorting: title, release date, file size, date added
- Saved searches β Bookmark filter/search combinations for quick access
- Pin download links across sessions
- Redirect chain resolution β Follows HTTP redirects, meta-refresh, iframes, and JS redirects to find the actual file URL
- Concurrent downloads with progress tracking, speed, ETA, and retry logic
- Provider labels β See which host (Mega, MediaFire, Dropbox, etc.) each link belongs to
PanelShelf includes a multi-layer strategy for scraping Cloudflare-protected sites:
| Layer | Method | When It's Used |
|---|---|---|
| 1. curl_cffi | TLS fingerprint impersonation (Chrome 120) | Lightweight, preferred β Docker sidecar |
| 2. Playwright | Full headless Chromium with stealth plugin | Fallback when curl_cffi fails |
| 3. FlareSolverr | Docker browser proxy (optional) | Last resort for stubborn sites |
- Add local folders β Point PanelShelf at a directory of comics
- Supported formats: CBZ (native), CBR (requires
unrar), ZIP, RAR - Automatic scanning β Recursively walks directories and indexes all comic files
- Cover caching β Extracts cover images on first scan, cached to disk
- Full-screen reader with keyboard shortcuts (next/prev, zoom modes, progress tracking)
- Reading progress β Persisted per-item, resumed across sessions
- Format badges β Each comic shows its format (CBZ/CBR) in the library grid
- Auto-scan on startup β Optional, scans all library folders when the server starts
- Tauri v2 native wrapper for macOS, Windows, and Linux
- Standalone backend β Backend compiled to a single binary via
@yao-pkg/pkg, runs as a sidecar - Health monitoring β Rust backend polls the API health endpoint, logs startup progress
- Graceful shutdown β Sidecar process is automatically killed when the desktop window closes
- Native folder dialogs β Use the OS directory picker for library folders
- Full OpenAPI documentation at
/docsin dev mode (powered by Scalar) - Dark-themed API docs β Branded custom CSS, dark mode, interactive "Try It" for each endpoint
- All catalog operations, source management, downloads, and library operations available via API
- Dark theme β Built with React, Tailwind CSS, and a polished dark gradient palette
- Toast notifications β In-app notifications for actions and errors
- Onboarding dialog β First-run setup guide for new users
- Responsive layout β Sidebar navigation, collapsible panels, hover states, and transitions
βββββββββββββββββββββββββββββββββββββ
β Web Browser / Tauri WebView β
β (React + TanStack Query + Vite) β
ββββββββββββββββ¬βββββββββββββββββββββ
β HTTP (REST API)
ββββββββββββββββΌβββββββββββββββββββββ
β PanelShelf Backend β
β (Fastify + TypeScript) β
β β
β βββββββββββββββββββββββββββββββ β
β β Provider Adapters β β
β β βββββββ ββββββ ββββββββββ β β
β β βRSS β βDCM β βZipComicβ β β
β β βββββββ€ ββββββ€ ββββββββββ€ β β
β β βGet β βIA β βManual β β β
β β βComs β β β βURL β β β
β β βββββββ ββββββ ββββββββββ β β
β βββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββΌβββββββββββββββββββββ β
β β Bypass Layer Stack β β
β β curl_cffi β Playwright β FS β β
β ββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββΌβββββββββββββββββββββ β
β β Catalog Service β β
β β (Drizzle ORM + SQLite) β β
β ββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββ β
β β Background Jobs (Queue) β β
β β Source refresh, indexing, β β
β β library scanning β β
β βββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript 5.7, Vite 6, TanStack React Query 5, React Router 7, Tailwind CSS 3, Lucide Icons |
| Backend | Fastify 5, TypeScript, Drizzle ORM 0.40, better-sqlite3, Cheerio (HTML parsing), undici (HTTP), rss-parser |
| Database | SQLite (single file, zero configuration, portable) |
| Desktop | Tauri v2, Rust (tauri-plugin-shell, tauri-plugin-dialog) |
| Package Manager | pnpm workspaces (monorepo) |
| CI | (Coming soon β GitHub Actions) |
| Auth | (Coming soon β JWT/Keycloak planned) |
panelshelf/
βββ packages/
β βββ frontend/ # React web application
β β βββ src/
β β β βββ api/ # API client functions & validation
β β β βββ components/ # React components (13+ components)
β β β βββ hooks/ # Custom React hooks
β β β βββ types/ # TypeScript type definitions
β β β βββ constants/ # App constants
β β β βββ utils/ # Utility functions
β β β βββ App.tsx # Root app with router
β β β βββ main.tsx # Entry point
β β βββ public/ # Static assets (favicon)
β βββ backend/ # Fastify API server
β β βββ src/
β β β βββ db/ # Drizzle schema, migrations, DB init
β β β βββ providers/ # 6+ provider adapters (scrapers)
β β β βββ routes/ # 7 route modules (catalog, sources, etc.)
β β β βββ services/ # Business logic (catalog, library scanner)
β β β βββ queue/ # Background job processing
β β β βββ config.ts # Environment config
β β β βββ index.ts # Server bootstrap
β β βββ sidecar/ # Python curl_cffi sidecar
β β βββ data/ # SQLite DB & downloads (gitignored)
β βββ desktop/ # Tauri v2 desktop wrapper (optional)
β βββ src-tauri/
β β βββ src/main.rs # Sidecar lifecycle, health polling
β β βββ binaries/ # Compiled backend placeholder
β β βββ capabilities/ # Tauri v2 permissions
β β βββ icons/ # App icons
β βββ scripts/ # Icon & backend build scripts
βββ Dockerfile # Multi-stage production build
βββ docker-compose.yml # Full stack with sidecar services
βββ package.json # Root scripts & workspace config
βββ pnpm-workspace.yaml # pnpm workspace definition
The backend maintains three in-memory caches for live search performance:
| Cache | TTL | Purpose |
|---|---|---|
| Feed URL cache | 1 hour | Resolved RSS/Atom feed URLs per source |
| Search results cache | 10 min | Scraped WP page results per query/page |
| Total pages cache | 10 min | Detected pagination count per source |
All caches can be cleared via Settings β Sources β Clear Cache, or POST /api/cache/clear.
- Node.js >= 22 (LTS recommended)
- pnpm >= 9 (install:
npm install -g pnpm) - Rust (only for desktop app:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh)
# Clone the repository (or download the source)
git clone https://github.com/me-cedric/panelshelf.git
cd panelshelf
# Install all dependencies
pnpm install
# Run database migrations (creates SQLite DB)
pnpm run db:migrate
# Start development servers (API + frontend concurrently)
pnpm devThe API starts at http://localhost:3001 and the frontend at http://localhost:5173.
Open http://localhost:5173 in your browser. The Vite dev server proxies /api requests to the backend.
# Build and start with Docker Compose (includes curl_cffi sidecar)
docker compose up -d
# Migrations run automatically on startup
# Open http://localhost:3001- Open the app in your browser
- Navigate to Sources (sidebar)
- Click Add Source to add a content source
Example: GetComics RSS feed
Name: GetComics
Type: RSS
Base URL: https://getcomics.org
Click Create Source, then click the Refresh button on the source row. The backend fetches and indexes comics from the source.
- Navigate to Catalog to browse indexed results and use live search.
Tip: Click the Detect button next to the URL field to auto-detect the source type from a URL.
Environment variables:
| Variable | Default | Description |
|---|---|---|
PORT |
3001 |
API server port |
HOST |
0.0.0.0 |
API server bind address |
DATA_DIR |
./data |
Directory for SQLite DB, cache, and downloads |
DOWNLOAD_DIR |
./data/downloads |
Download destination |
MAX_CONCURRENT_DOWNLOADS |
3 |
Max simultaneous downloads |
DEFAULT_REFRESH_INTERVAL_MIN |
60 |
Default source refresh interval (minutes) |
AUTO_SCAN_ON_START |
false |
Auto-scan library sources on server start |
CURL_CFFI_URL |
β | curl_cffi sidecar endpoint for Cloudflare bypass |
FLARESOLVERR_URL |
β | FlareSolverr endpoint (alternative bypass) |
LOG_LEVEL |
info |
Pino log level (trace/debug/info/warn/error/fatal) |
NODE_ENV |
development |
Set to production for production mode |
PanelShelf includes a full-featured comic reader for browsing and reading your local collection directly in the browser or desktop app.
| Format | Extension | Backend |
|---|---|---|
| CBZ (ZIP-compressed) | .cbz, .zip |
adm-zip (native) |
| CBR (RAR-compressed) | .cbr, .rar |
unrar or unar (system CLI) |
.pdf |
Planned (see roadmap) |
Note: For CBR/RAR support, install
unrar(macOS:brew install unrar, Ubuntu:sudo apt install unrar, Windows: download from rarlab.com) orunar(brew install unar/sudo apt install unar).
| Action | Key |
|---|---|
| Next page | β / Space |
| Previous page | β |
| First page | Home |
| Last page | End |
| Fit to width | F (cycles: width β height β original) |
| Original size | Z |
| Toggle shortcuts | ? |
| Exit reader | Esc |
Reading progress is saved automatically between sessions. Finished comics show a green checkmark badge.
PanelShelf includes an optional Tauri v2 desktop wrapper that packages the web app as a native desktop application.
The desktop app bundles:
- Frontend β Built static files served by Tauri's webview
- Backend β Compiled to a standalone binary (
@yao-pkg/pkg), runs as a sidecar process - Tauri β Rust wrapper that spawns the backend, monitors its health via
/api/health, and kills it on window close
cd packages/desktop
# Generate app icons from the SVG favicon
pnpm run icon
# Development mode (uses tsx for backend, hot-reloads frontend)
pnpm run tauri:dev
# Production build (compiles backend, builds frontend, packages .dmg/.msi/.AppImage)
pnpm run tauri:build| Platform | Requirements |
|---|---|
| macOS | Xcode Command Line Tools (xcode-select --install) |
| Linux | sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev |
| Windows | Microsoft Visual Studio C++ Build Tools |
- The
@yao-pkg/pkgbundler may not fully supportbetter-sqlite3's native module. If the production build fails, use Docker-based distribution or try Bun's--compileoption.
The included Dockerfile uses a multi-stage build:
- Frontend builder β Installs deps, builds Vite output
- Backend builder β Installs deps, compiles TypeScript
- Runtime β Minimal
node:20-alpinewithdumb-init, production deps only
Run with Docker Compose:
docker compose up -d
# Open http://localhost:3001The Compose file also includes:
- curl-cffi-sidecar β Python-based TLS fingerprint bypass (enabled by default)
- FlareSolverr β Optional full-browser proxy (commented out β uncomment to enable)
# Start dev servers (API + frontend concurrently)
pnpm dev
# Type-check all packages
pnpm run typecheck
# Build all packages (backend + frontend)
pnpm run build
# Run database migrations
pnpm run db:migrate
# Generate Drizzle migrations (after schema changes)
pnpm --filter @panelshelf/backend run db:generate
# Push schema changes directly (dev only β SQLite)
pnpm --filter @panelshelf/backend run db:push
# Desktop development
pnpm run dev:desktop
# Desktop production build
pnpm run build:desktopProvider adapters follow the ProviderAdapter interface in packages/backend/src/providers/types.ts:
interface ProviderAdapter {
id: string;
name: string;
canHandle(url: string): boolean;
inspect(url: string, options?: ProviderInspectOptions): Promise<ProviderInspectionResult>;
download(request: DownloadRequest): Promise<DownloadResult>;
}To add a new provider:
- Create
packages/backend/src/providers/<name>/index.ts - Implement
ProviderAdapter - Register it in
packages/backend/src/index.tsviaregisterProvider() - Optionally add auto-detection logic in
packages/backend/src/routes/sources.ts
If you don't want to use Docker, run the Python sidecar locally:
cd packages/backend/sidecar
pip install -r requirements.txt
python3 server.py
# Then set: CURL_CFFI_URL=http://localhost:8192/fetchPanelShelf exposes a REST API at http://localhost:3001/api. In dev mode, interactive API documentation is available at http://localhost:3001/docs (branded dark theme).
| Method | Path | Description |
|---|---|---|
| System | ||
GET |
/api/health |
Health check (status, version, uptime) |
GET |
/api/stats |
Catalog stats (total items, sources, downloads) |
GET |
/api/settings |
Get application settings |
POST |
/api/settings |
Update application settings |
| Catalog | ||
GET |
/api/catalog |
List/search indexed catalog items |
GET |
/api/catalog/:id |
Get single item with download links |
GET |
/api/catalog/filters/:column |
Get distinct filter values |
GET |
/api/catalog/live-search |
Live search across source feeds |
GET |
/api/catalog/live-search/categories |
List provider categories (e.g., DCM publishers) |
GET |
/api/catalog/live-detail |
Scrape download links from a detail page |
GET |
/api/catalog/resolve-download |
Resolve a download URL through redirects |
| Sources | ||
GET |
/api/sources |
List configured sources |
POST |
/api/sources |
Create a source |
PUT |
/api/sources/:id |
Update a source |
DELETE |
/api/sources/:id |
Delete a source |
POST |
/api/sources/:id/refresh |
Trigger source refresh |
POST |
/api/sources/auto-detect |
Auto-detect source type from URL |
GET |
/api/sources/types |
List available source types |
| Downloads | ||
GET |
/api/downloads |
List downloads |
POST |
/api/downloads |
Enqueue a download |
| Saved Searches | ||
GET |
/api/saved-searches |
List saved searches |
POST |
/api/saved-searches |
Save a search |
DELETE |
/api/saved-searches/:id |
Delete a saved search |
| Cache | ||
POST |
/api/cache/clear |
Clear all in-memory provider caches |
| Library | ||
GET |
/api/library/sources |
List library sources (folders) |
POST |
/api/library/sources |
Add a library folder |
DELETE |
/api/library/sources/:id |
Remove a library folder |
POST |
/api/library/sources/:id/scan |
Scan a library folder for comics |
GET |
/api/library/items |
List library items with search/filter |
GET |
/api/library/items/:id/page/:page |
Get a single page image |
GET |
/api/library/items/:id/cover |
Get cover image |
POST |
/api/library/scan-all |
Scan all library sources |
| Providers | ||
GET |
/api/providers |
List registered providers |
# Browse all posts from an RSS source
curl "http://localhost:3001/api/catalog/live-search?sourceId=<source-id>&page=1"
# Search with term
curl "http://localhost:3001/api/catalog/live-search?sourceId=<source-id>&q=batman&page=1"
# Fresh data (bypass cache)
curl "http://localhost:3001/api/catalog/live-search?sourceId=<source-id>&q=batman&fresh=true"# Type-check all packages
pnpm run typecheck
# Run backend type-check
pnpm --filter @panelshelf/backend run typecheck
# Run frontend type-check
pnpm --filter @panelshelf/frontend run typecheckUnit test infrastructure is planned. Contributions welcome!
Contributions are welcome! Here's how to help:
- Open a GitHub Issue
- Include steps to reproduce for bugs
- Describe the use case for feature requests
- Fork the repository
- Create a feature branch (
git checkout -b feat/amazing-feature) - Make your changes
- Run
pnpm run typecheckto verify no type errors - Commit (
git commit -m 'feat: add amazing feature') - Push to your fork (
git push origin feat/amazing-feature) - Open a Pull Request
- Follow the existing code style and architecture patterns
- Provider adapters go behind the existing
ProviderAdapterinterface - Run
pnpm run typecheckbefore committing - Update this README if adding significant features
- Use Conventional Commits style (
feat:,fix:,chore:,docs:, etc.)
See the Development section above. The project uses a standard pnpm monorepo β install, migrate, and run.
- Unit & integration tests (Vitest for frontend, Node Test Runner for backend)
- GitHub Actions CI β Type-check, lint, test on PRs
- JWT / Keycloak authentication
- User account management with per-user libraries
- PDF comic support in the reader
- OPDS feed for external reader apps (e.g., Chunky, YACReader)
- WebSocket push for real-time download progress
- Docker Compose health checks for all services
- Comic reader β Full-screen page viewer
- Desktop app β Tauri v2 native wrapper
- Cloudflare bypass β curl_cffi + Playwright + FlareSolverr
- Library management β Local folder scanning
- Download manager β Queue, progress, redirect resolution
- Multi-source aggregation β RSS, DCM, IA, ZipComic, GetComics
Distributed under the MIT License. See LICENSE for more information.
- GetComics β Primary comic release aggregator
- Digital Comic Museum β Public domain comic archive
- Internet Archive β Digital library
- ZipComic β Comic download portal
- Built with Fastify, React, Tauri, Drizzle, and many other open-source libraries
GitHub β’ Docs β’ Quick Start β’ Contributing
Built with β€οΈ and TypeScript, Rust, and Python