Search and download books from your girl's favorite archive. Includes a request system to auto-download books once they're available. Supports auto-move to a BookLore or Calibre-Web-Automated ingest folder or BookLore API upload.
- Download books from any archive
- Use donator key for super fast downloads or a l----n library for fast free downloads (also supports slow downloads as a fallback)
- Automatically import books to BookLore or Calibre-Web-Automated by utilizing their ingest folders and/or upload APIs
- Request system to auto download non-available books once they become available
- Notifications on newly available books or fulfilled requests with Apprise
- Implement Ephemera as a usenet indexer into newznab tools like Readarr
- Realtime updates in UI
- Supports all popular book formats (epub, awz3, mobi, pdf, cbz, cbr etc.)
- Link your BookLore or CWA library in the menu
- OpenAPI specs for 3rd party integrations, Swagger-UI
- Simple setup with Docker
- Cloudflare bypassing with Flaresolverr
services:
ephemera:
image: ghcr.io/orwellianepilogue/ephemera:latest
container_name: ephemera
restart: unless-stopped
ports:
- "8286:8286"
environment:
# Required:
AA_BASE_URL:
# FlareSolverr is used for slow download fallback when API key is missing or quota exhausted
# Default: http://127.0.0.1:8191 (or http://flaresolverr:8191 in Docker)
FLARESOLVERR_URL: http://127.0.0.1:8191
# Optional
# Alternative Download Source (optional, but highly recommended)
# If set, LG fast download will be attempted before falling back to slow servers
# Leave empty to disable this feature
# li, bz, etc.
LG_BASE_URL: #https://gen.com
AA_API_KEY:
PUID: 1000
PGID: 100
volumes:
- ./data:/app/data
- ./downloads:/app/downloads
- ./ingest:/app/ingest
# Set DNS server to prevent EU blocking
#dns:
# - 1.1.1.1
# - 1.0.0.1
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://127.0.0.1:8286/health",
]
interval: 30s
timeout: 10s
start_period: 40s
retries: 3
flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
container_name: flaresolverr
restart: unless-stopped
environment:
- LOG_LEVEL=info
- LOG_HTML=false
- CAPTCHA_SOLVER=none
- TZ=Europe/Berlin
# Set DNS server to prevent EU blocking
#dns:
# - 1.1.1.1
# - 1.0.0.1
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:8191/health"]
interval: 30s
timeout: 10s
start_period: 30s
retries: 3The following Docker image tags are available:
| Tag | Description | Update Frequency |
|---|---|---|
latest |
Latest stable release | On version tags (v*.*.* releases) |
dev |
Latest development build | On commits to dev branch |
1.2.3 |
Specific version | Never (immutable) |
1.2 |
Latest patch of minor version | On patch releases |
1 |
Latest minor/patch of major version | On minor/patch releases |
dev-sha-abc1234 |
Specific dev build | Never (immutable) |
# Production (stable release)
docker pull ghcr.io/orwellianepilogue/ephemera:latest
# Development (latest dev branch)
docker pull ghcr.io/orwellianepilogue/ephemera:dev
# Specific version
docker pull ghcr.io/orwellianepilogue/ephemera:1.2.2
# Specific dev build
docker pull ghcr.io/orwellianepilogue/ephemera:dev-sha-7aa9d68Note: The
latesttag always points to the most recent stable release. If you want to test new features before they're released, use thedevtag.
Only two variables are required:
| Variable | Description | Example |
|---|---|---|
AA_BASE_URL |
Base URL of your archive | https://yourarchive.org |
FLARESOLVERR_URL |
Flaresolverr URL from container | http://127.0.0.1:8191 |
| Variable | Default | Description |
|---|---|---|
LG_BASE_URL |
empty |
https://gen.com, li, bz etc. |
All other settings have sensible defaults, but you can override them:
| Variable | Default | Description |
|---|---|---|
AA_API_KEY |
empty |
dhw8adhwa8... |
PORT |
8286 |
Application port |
DB_PATH |
/app/data/database.db |
Database location |
DOWNLOAD_FOLDER |
/app/downloads |
Temp download folder |
INGEST_FOLDER |
/app/ingest |
Final books folder |
NODE_ENV |
production |
Node environment |
RETRY_ATTEMPTS |
3 |
Download retries |
REQUEST_TIMEOUT |
30000 |
API timeout (ms) |
SEARCH_CACHE_TTL |
300 |
Search cache (seconds) |
ephemera/
├── packages/
│ ├── api/ # Hono API backend with Crawlee scraping
│ ├── shared/ # Shared TypeScript types and API client
│ └── web/ # React frontend with Vite
└── data/ # SQLite database
- Node.js 22+
- pnpm 9+
# Install all dependencies
pnpm install
# Approve build scripts for native modules
pnpm approve-builds
# Select: better-sqlite3, esbuild
# Copy environment template
cp packages/api/.env.example packages/api/.env
# Edit with your AA API key and url
nano packages/api/.env
# Run migrations
cd packages/api && pnpm db:migrate# Run everything (API + Frontend)
pnpm dev
# Or run individually:
pnpm dev:api # Backend only (http://localhost:8286)
pnpm dev:web # Frontend only (http://localhost:5173)# Build all packages
pnpm build
# Build individually
pnpm build:api
pnpm build:web- Framework: Hono 4.6+ (lightweight, fast, type-safe)
- Database: SQLite + Drizzle ORM
- Scraping: Crawlee + Cheerio
- Validation: Zod schemas
- OpenAPI: Swagger UI and auto-generated spec at
http://host:8286/api/docs&http://host:8286/api/openapi.json
- Framework: React 18 + TypeScript
- Build Tool: Vite 6
- UI Library: Mantine UI 7
- Routing: TanStack Router v1
- Data Fetching: TanStack Query v5
- Icons: Tabler Icons
- Schemas: Zod validation schemas
- Types: TypeScript types (exported from Zod)
- API Client: Typed fetch wrapper using OpenAPI types
- Type Generation:
openapi-typescriptfrom live API
Full end-to-end type safety:
API (Zod schemas) → OpenAPI spec → TypeScript types → React frontend
Changes to the API automatically propagate to the frontend through:
- Zod schemas in
packages/shared/src/schemas.ts - Generated OpenAPI types via
openapi-typescript - Type-safe client in
packages/shared/src/client.ts
pnpm dev # Run all packages in parallel
pnpm build # Build all packages
pnpm type-check # Type-check all packages
pnpm clean # Clean all build artifactspnpm --filter @ephemera/api dev # Dev mode with watch
pnpm --filter @ephemera/api build # Build TypeScript
pnpm --filter @ephemera/api db:generate # Generate migrations
pnpm --filter @ephemera/api db:migrate # Run migrations
pnpm --filter @ephemera/api db:studio # Open Drizzle Studiopnpm --filter @ephemera/web dev # Dev server with HMR
pnpm --filter @ephemera/web build # Production build
pnpm --filter @ephemera/web preview # Preview prod buildpnpm --filter @ephemera/shared build # Build types
pnpm --filter @ephemera/shared generate:client # Generate API typesThis monorepo uses Changesets for synchronized versioning. All packages (api, web, shared) always share the same version number.
- Make your changes and commit them normally
- Create a changeset describing what changed:
pnpm changeset # Follow prompts: select change type (patch/minor/major) and write summary - When ready to release, run:
pnpm release # This will: version packages → create git tag → push to trigger Docker build
# Create a changeset (describes your changes)
pnpm changeset
# Check status of pending changesets
pnpm changeset:status
# Apply changesets and update versions + CHANGELOG
pnpm version
# Full release (version → tag → push)
pnpm release
# Individual release steps (if you want more control)
pnpm release:version # Update package.json + CHANGELOG
pnpm release:tag # Commit changes and create git tag
pnpm release:push # Push code and tags to trigger Docker build- Changesets track what changed between versions
- Synchronized versioning: All packages version together (currently at v1.0.3)
- Automatic changelog: Generated from changeset summaries
- Docker automation: Pushing a git tag (e.g.,
v1.0.4) triggers the GitHub Action to build and publish a new Docker image
# 1. After implementing a new feature
git add .
git commit -m "feat: add book metadata export"
# 2. Create a changeset
pnpm changeset
# → Select "minor" (new feature)
# → Enter summary: "Add book metadata export functionality"
# 3. Continue working, create more changesets for other changes
pnpm changeset
# → Select "patch" (bug fix)
# → Enter summary: "Fix download progress bar display"
# 4. When ready to release
pnpm release
# → All changesets consumed
# → package.json versions bumped (e.g., 1.0.3 → 1.1.0)
# → CHANGELOG.md updated
# → Git tag v1.1.0 created
# → Changes pushed to GitHub
# → Docker build triggered automatically- patch (1.0.3 → 1.0.4): Bug fixes, minor tweaks
- minor (1.0.3 → 1.1.0): New features, backwards-compatible changes
- major (1.0.3 → 2.0.0): Breaking changes
Edit packages/shared/src/schemas.ts:
export const myNewSchema = z.object({
id: z.string(),
name: z.string(),
});import { myNewSchema } from "@ephemera/shared";
const route = createRoute({
request: { body: myNewSchema },
// ...
});# Start API first
pnpm dev:api
# In another terminal, generate types
pnpm --filter @ephemera/shared generate:clientimport { client } from "@ephemera/shared";
const data = await client.get("/api/new-endpoint");
// `data` is fully typed!- Swagger UI: http://localhost:8286/api/docs
- OpenAPI Spec: http://localhost:8286/api/openapi.json
/- Search books/queue- Download queue management/settings- App and Booklore settings
The frontend proxies /api/* requests to the backend during development:
// vite.config.ts
proxy: {
'/api': {
target: 'http://localhost:8286',
changeOrigin: true,
},
}MIT



