From 266b57c38a2413fa1ec6f542db796999471c2818 Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Wed, 13 May 2026 01:35:58 -0600 Subject: [PATCH] chore: reorganize docs, improve README, and add FOSS project hygiene --- .env.example | 6 +- .github/ISSUE_TEMPLATE/config.yml | 7 ++ .github/labeler.yml | 33 ++++++ .github/workflows/labeler.yml | 20 ++++ .github/workflows/stale.yml | 40 +++++++ README.md | 112 +++++++++++------- client/package.json | 10 ++ AI_POLICY.md => docs/AI_POLICY.md | 0 CODE_OF_CONDUCT.md => docs/CODE_OF_CONDUCT.md | 0 .../COMMIT_GUIDELINES.md | 0 CONTRIBUTING.md => docs/CONTRIBUTING.md | 0 DEVELOPMENT.md => docs/DEVELOPMENT.md | 2 +- ...{release-process.md => RELEASE_PROCESS.md} | 2 +- TESTING.md => docs/TESTING.md | 0 package.json | 11 ++ server/package.json | 12 +- .../user-statistics.repository.ts | 4 +- 17 files changed, 209 insertions(+), 50 deletions(-) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/labeler.yml create mode 100644 .github/workflows/stale.yml rename AI_POLICY.md => docs/AI_POLICY.md (100%) rename CODE_OF_CONDUCT.md => docs/CODE_OF_CONDUCT.md (100%) rename COMMIT_GUIDELINES.md => docs/COMMIT_GUIDELINES.md (100%) rename CONTRIBUTING.md => docs/CONTRIBUTING.md (100%) rename DEVELOPMENT.md => docs/DEVELOPMENT.md (99%) rename docs/{release-process.md => RELEASE_PROCESS.md} (98%) rename TESTING.md => docs/TESTING.md (100%) diff --git a/.env.example b/.env.example index 457dac620..00cd4384b 100644 --- a/.env.example +++ b/.env.example @@ -13,9 +13,6 @@ BOOKS_HOST_PATH=./books PUID=1000 PGID=1000 -# Node.js JavaScript heap limit in MB. Raise this for very large libraries. -NODE_MAX_OLD_SPACE_SIZE=2048 - # Database credentials POSTGRES_USER=bookorbit POSTGRES_PASSWORD=change-this-password @@ -29,6 +26,9 @@ SETUP_BOOTSTRAP_TOKEN=change-this-to-a-long-random-secret # External/public URL used by emails and Kobo endpoints APP_URL=https://bookorbit.example.com +# Node.js JavaScript heap limit in MB. Raise this for very large libraries. +NODE_MAX_OLD_SPACE_SIZE=2048 + # --- Optional overrides (uncomment to set) --- # If you already run your own PostgreSQL, set DATABASE_URL and remove the diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3ba13e0ce..0bbb3c7c5 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,8 @@ blank_issues_enabled: false +contact_links: + - name: Questions and Discussions + url: https://github.com/bookorbit/bookorbit/discussions + about: Ask questions, get help with your setup, or share how you use BookOrbit + - name: Documentation + url: https://bookorbit.app + about: Read the full documentation at bookorbit.app diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..826e7209b --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,33 @@ +server: + - changed-files: + - any-glob-to-any-file: "server/**" + +client: + - changed-files: + - any-glob-to-any-file: "client/**" + +packages: + - changed-files: + - any-glob-to-any-file: "packages/**" + +docs: + - changed-files: + - any-glob-to-any-file: "docs/**" + - any-glob-to-any-file: "*.md" + +ci: + - changed-files: + - any-glob-to-any-file: ".github/**" + - any-glob-to-any-file: "scripts/**" + - any-glob-to-any-file: "docker/**" + - any-glob-to-any-file: "Dockerfile" + - any-glob-to-any-file: "docker-compose*.yml" + +dependencies: + - changed-files: + - any-glob-to-any-file: "package.json" + - any-glob-to-any-file: "pnpm-lock.yaml" + - any-glob-to-any-file: "pnpm-workspace.yaml" + - any-glob-to-any-file: "server/package.json" + - any-glob-to-any-file: "client/package.json" + - any-glob-to-any-file: "packages/*/package.json" diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 000000000..9d2ff99bf --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,20 @@ +name: PR Labeler + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + +jobs: + label: + name: Label PR by Changed Paths + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + configuration-path: .github/labeler.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..bffe0539a --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,40 @@ +name: Stale Issues and PRs + +on: + schedule: + - cron: "0 3 * * *" + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + name: Mark and Close Stale + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/stale@5bef64f19d7facfed25c7e059aefc10cb5ab2bf2 # v9 + with: + days-before-issue-stale: 60 + days-before-issue-close: 14 + days-before-pr-stale: 30 + days-before-pr-close: 14 + stale-issue-label: stale + stale-pr-label: stale + exempt-issue-labels: "pinned,security" + exempt-pr-labels: pinned + stale-issue-message: > + This issue has been inactive for 60 days. It will be closed in 14 days + unless there is new activity. If this is still relevant, please leave a + comment or update the issue. + stale-pr-message: > + This PR has been inactive for 30 days. It will be closed in 14 days + unless there is new activity. If you are still working on this, please + leave a comment or push an update. + close-issue-message: > + Closing due to inactivity. Feel free to reopen if this is still relevant. + close-pr-message: > + Closing due to inactivity. Feel free to reopen if you want to continue + this work. diff --git a/README.md b/README.md index 0bfdde55e..11b16af80 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,25 @@
- -BookOrbit - # BookOrbit -A self-hosted library management and reading platform for epub, pdf, audiobooks, and comics. +A self-hosted library management and reading platform for ebooks, PDFs, audiobooks, and comics. +[![Stars](https://img.shields.io/github/stars/bookorbit/bookorbit?style=flat)](https://github.com/bookorbit/bookorbit/stargazers) [![CI](https://github.com/bookorbit/bookorbit/actions/workflows/ci.yml/badge.svg)](https://github.com/bookorbit/bookorbit/actions/workflows/ci.yml) [![Release](https://github.com/bookorbit/bookorbit/actions/workflows/release.yml/badge.svg)](https://github.com/bookorbit/bookorbit/actions/workflows/release.yml) [![Latest release](https://img.shields.io/github/v/release/bookorbit/bookorbit?label=latest)](https://github.com/bookorbit/bookorbit/releases) [![Commits/month](https://img.shields.io/github/commit-activity/m/bookorbit/bookorbit?label=commits%2Fmonth)](https://github.com/bookorbit/bookorbit/commits/main) -[![Stars](https://img.shields.io/github/stars/bookorbit/bookorbit?style=flat)](https://github.com/bookorbit/bookorbit/stargazers) [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](LICENSE) -[![Container image](https://img.shields.io/badge/ghcr.io-bookorbit%2Fbookorbit-informational?logo=docker&logoColor=white)](https://github.com/bookorbit/bookorbit/pkgs/container/bookorbit) -**[Documentation](https://bookorbit.app) · [Live Demo](https://demo.bookorbit.app) · [Report a Bug](https://github.com/bookorbit/bookorbit/issues/new?template=bug_report.yml) · [Request a Feature](https://github.com/bookorbit/bookorbit/issues/new?template=feature_request.yml)** + + +[![Website](https://img.shields.io/badge/Website-bookorbit.app-blue?style=flat&logo=googlechrome&logoColor=white)](https://bookorbit.app) +[![Demo](https://img.shields.io/badge/Demo-live-brightgreen?style=flat&logo=rocket&logoColor=white)](https://demo.bookorbit.app) +[![Discussions](https://img.shields.io/badge/Discussions-GitHub-333?style=flat&logo=github&logoColor=white)](https://github.com/bookorbit/bookorbit/discussions) +[![Contributing](https://img.shields.io/badge/Contributing-guide-orange?style=flat&logo=handshake&logoColor=white)](https://github.com/bookorbit/bookorbit/blob/main/docs/CONTRIBUTING.md) +[![GHCR](https://img.shields.io/badge/GHCR-bookorbit/bookorbit-blue?style=flat&logo=docker&logoColor=white)](https://github.com/bookorbit/bookorbit/pkgs/container/bookorbit)
@@ -25,60 +29,86 @@ A self-hosted library management and reading platform for epub, pdf, audiobooks, --- -## About +## What is BookOrbit? -BookOrbit is a self-hosted book and library manager. Deploy it once on your own server and get a full reading platform: built-in readers, rich metadata, reading statistics, device sync, and multi-user support - all running on your hardware. - -No subscription. No third-party account. No data leaving your server. - ---- - -## Live Demo - -Try BookOrbit without installing anything. The demo is pre-loaded with a sample library and resets automatically. - -**[Open demo](https://demo.bookorbit.app/magic?token=2d92cb900e184cf0eb8b11f72cffc6011673d1016e1b300d750eb3d76abc1572)** - no account needed, click the link to log in automatically. +BookOrbit is a self-hosted digital library and reading platform. Organize and read your books, sync seamlessly with Kobo and KOReader devices, enrich your collection with metadata from multiple providers, and support multiple users with OIDC/SSO authentication and detailed reading statistics. Built-in features include OPDS support, customizable dashboard widgets, Send-to-Kindle delivery, and Smart Scopes for dynamic rule-based shelves and filters - all running on infrastructure you control. --- ## Features -- **4 built-in readers** - eBook (EPUB, KEPUB, MOBI, AZW3, FB2), PDF, Comics (CBZ, CBR, CB7), Audiobook (M4B, MP3, FLAC, and more) -- **Multiple libraries** with independent scan rules, format priorities, and metadata configuration -- **Collections and Smart Scopes** - curated lists and saved filters that live in your sidebar -- **Metadata from 9 providers** - Google Books, Goodreads, Hardcover, Amazon, Audible, ComicVine, and more; with field-level rules and confidence scoring -- **Reading statistics** - daily time, heatmap, streaks, pace, goal tracking, and library health -- **Kobo sync** - sideload books and sync reading position over the local network -- **OPDS, email delivery, and Book Dock** for getting books in from anywhere -- **Multi-user** with per-user permissions, isolated reading data, and OIDC/SSO support +**Built-in readers** - no plugins or extra installs required: + +| Reader | Formats | +| --------- | --------------------------------- | +| eBook | EPUB, KEPUB, MOBI, AZW3, AZW, FB2 | +| PDF | PDF | +| Comics | CBZ, CBR, CB7 | +| Audiobook | M4B, MP3, M4A, OPUS, OGG, FLAC | + +**Multiple libraries:** Per-library folders, scan rules, format priorities, metadata config, and file-write settings. + +**Metadata from 9 providers:** Google Books, Amazon, Goodreads, Hardcover, Open Library, iTunes, Audible, AudNexus, and ComicVine - with field-level rules. + +**Collections and Smart Scopes:** Curated lists and rule-based saved filters in the sidebar. + +**Kobo and KOReader sync:** Auto-push books to Kobo; two-way reading progress sync via KOReader over OPDS. + +**OPDS, email delivery, and Book Dock:** OPDS for compatible apps, Send-to-Kindle via email, and browser drag-and-drop uploads. + +**Multi-user with multi-provider OIDC/SSO:** Granular per-user permissions, isolated reading data, and simultaneous support for Authentik, Keycloak, Authelia, etc. + +**Reading statistics:** Daily reading time, heatmap, streaks, pace, goal tracking, and library health dashboard. --- -## Setup +## Live Demo -See the **[Installation guide](https://bookorbit.app/installation.html)** for Docker setup, NAS installs, environment variables, and upgrade instructions. +Try BookOrbit without installing anything. + +**[Open Demo](https://demo.bookorbit.app/magic?token=2d92cb900e184cf0eb8b11f72cffc6011673d1016e1b300d750eb3d76abc1572)** - no account needed, the link logs you in automatically. + +The demo is pre-loaded with a sample library of public domain books and resets automatically. --- -## Documentation +## Quick Start -Full documentation is at **[bookorbit.app](https://bookorbit.app)**. +```bash +mkdir bookorbit && cd bookorbit +mkdir -p books data/app data/postgres +curl -fsSLo .env https://raw.githubusercontent.com/bookorbit/bookorbit/main/.env.example +curl -fsSLo docker-compose.yml https://raw.githubusercontent.com/bookorbit/bookorbit/main/docker-compose.yml +``` -> [What is BookOrbit?](https://bookorbit.app/what-is-bookorbit.html)  ·  [Installation](https://bookorbit.app/installation.html)  ·  [Creating a Library](https://bookorbit.app/creating-a-library.html)  ·  [Metadata](https://bookorbit.app/metadata.html)  ·  [Kobo Sync](https://bookorbit.app/kobo.html)  ·  [Users & Permissions](https://bookorbit.app/users.html) +Edit `.env` and set these required values: + +```dotenv +APP_URL=http://your-server-ip:3000 # the URL you'll open in your browser +BOOKS_HOST_PATH=./books # folder on your server where your book files live + +POSTGRES_PASSWORD= # database password - openssl rand -hex 24 +JWT_SECRET= # signs login tokens - openssl rand -hex 32 +SETUP_BOOTSTRAP_TOKEN= # one-time setup wizard token - openssl rand -hex 16 +``` + +Then start: + +```bash +docker compose up -d +``` + +Open `http://your-server-ip:3000` and complete setup using your `SETUP_BOOTSTRAP_TOKEN`. + +For the full installation guide including reverse proxy setup, file permissions on NAS, external databases, and environment variable reference, see **[bookorbit.app/installation](https://bookorbit.app/installation.html)**. --- -## Developing +## Documentation and Contributing -See **[DEVELOPMENT.md](DEVELOPMENT.md)** for local setup, architecture, project structure, database workflow, and commands. See **[TESTING.md](TESTING.md)** for test suites, coverage, and E2E details. +Full documentation is at **[bookorbit.app](https://bookorbit.app)** - covering libraries, metadata, readers, Kobo sync, OPDS, users and permissions, OIDC setup, and more. ---- - -## Contributing - -Contributions are welcome. For the full contribution workflow (branch naming, test expectations, PR checklist, commit format), see **[CONTRIBUTING.md](CONTRIBUTING.md)**. - -To report a security vulnerability, use [GitHub's private vulnerability reporting](https://github.com/bookorbit/bookorbit/security/advisories/new) - do not open a public issue. +For local development, see [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md). To contribute, see [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for the full workflow: branch naming, test expectations, PR checklist, and commit format. --- diff --git a/client/package.json b/client/package.json index 730e7331e..b652458ff 100644 --- a/client/package.json +++ b/client/package.json @@ -1,8 +1,18 @@ { "name": "client", "version": "0.0.0", + "description": "BookOrbit client - self-hosted library management and reading platform", + "author": "neonsolstice", "private": true, "license": "AGPL-3.0", + "homepage": "https://bookorbit.app", + "repository": { + "type": "git", + "url": "https://github.com/bookorbit/bookorbit" + }, + "bugs": { + "url": "https://github.com/bookorbit/bookorbit/issues" + }, "type": "module", "scripts": { "dev": "vite", diff --git a/AI_POLICY.md b/docs/AI_POLICY.md similarity index 100% rename from AI_POLICY.md rename to docs/AI_POLICY.md diff --git a/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to docs/CODE_OF_CONDUCT.md diff --git a/COMMIT_GUIDELINES.md b/docs/COMMIT_GUIDELINES.md similarity index 100% rename from COMMIT_GUIDELINES.md rename to docs/COMMIT_GUIDELINES.md diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to docs/CONTRIBUTING.md diff --git a/DEVELOPMENT.md b/docs/DEVELOPMENT.md similarity index 99% rename from DEVELOPMENT.md rename to docs/DEVELOPMENT.md index a6e5349e9..2a455a97b 100644 --- a/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -379,7 +379,7 @@ Releases are created manually via `workflow_dispatch` on the `release.yml` workf PR titles are validated by commitlint to ensure they follow the [commit guidelines](COMMIT_GUIDELINES.md). Only releasable types (`feat`, `fix`, `perf`, `security`, `db`, `style`) trigger version bumps. -For the full release runbook, see [`docs/release-process.md`](docs/release-process.md). +For the full release runbook, see [`docs/RELEASE_PROCESS.md`](docs/RELEASE_PROCESS.md). ### E2E in CI diff --git a/docs/release-process.md b/docs/RELEASE_PROCESS.md similarity index 98% rename from docs/release-process.md rename to docs/RELEASE_PROCESS.md index 03e7d444a..446894766 100644 --- a/docs/release-process.md +++ b/docs/RELEASE_PROCESS.md @@ -22,7 +22,7 @@ All PRs to `main` must use **squash merge**. The squash commit message becomes t ### PR title format -PR titles must follow the [commit guidelines](../COMMIT_GUIDELINES.md): +PR titles must follow the [commit guidelines](COMMIT_GUIDELINES.md): ``` (): diff --git a/TESTING.md b/docs/TESTING.md similarity index 100% rename from TESTING.md rename to docs/TESTING.md diff --git a/package.json b/package.json index f5e425b0e..dc234f775 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,16 @@ { "name": "bookorbit", + "description": "Self-hosted library management and reading platform for ebooks, PDFs, audiobooks, and comics", + "author": "neonsolstice", + "license": "AGPL-3.0", + "homepage": "https://bookorbit.app", + "repository": { + "type": "git", + "url": "https://github.com/bookorbit/bookorbit" + }, + "bugs": { + "url": "https://github.com/bookorbit/bookorbit/issues" + }, "private": true, "packageManager": "pnpm@10.32.1", "scripts": { diff --git a/server/package.json b/server/package.json index 34449f050..a843bcfc5 100644 --- a/server/package.json +++ b/server/package.json @@ -1,10 +1,18 @@ { "name": "server", "version": "0.0.1", - "description": "", - "author": "", + "description": "BookOrbit server - self-hosted library management and reading platform", + "author": "neonsolstice", "private": true, "license": "AGPL-3.0", + "homepage": "https://bookorbit.app", + "repository": { + "type": "git", + "url": "https://github.com/bookorbit/bookorbit" + }, + "bugs": { + "url": "https://github.com/bookorbit/bookorbit/issues" + }, "scripts": { "prestart": "pnpm --filter @bookorbit/types build", "prestart:dev": "pnpm --filter @bookorbit/types build", diff --git a/server/src/modules/user-statistics/user-statistics.repository.ts b/server/src/modules/user-statistics/user-statistics.repository.ts index db218f914..7ef51d59f 100644 --- a/server/src/modules/user-statistics/user-statistics.repository.ts +++ b/server/src/modules/user-statistics/user-statistics.repository.ts @@ -387,7 +387,7 @@ export class UserStatisticsRepository { .from(readingSessions) .innerJoin(bookFiles, eq(bookFiles.id, readingSessions.bookFileId)) .innerJoin(books, eq(books.id, bookFiles.bookId)) - .where(and(eq(readingSessions.userId, userId), gte(readingSessions.endProgress, 100), libraryFilter)) + .where(and(eq(readingSessions.userId, userId), gte(readingSessions.endProgress, 99), libraryFilter)) .groupBy(bookFiles.bookId) .as('first_completion'); @@ -467,7 +467,7 @@ export class UserStatisticsRepository { .from(readingSessions) .innerJoin(bookFiles, eq(bookFiles.id, readingSessions.bookFileId)) .innerJoin(books, eq(books.id, bookFiles.bookId)) - .where(and(eq(readingSessions.userId, userId), gte(readingSessions.startedAt, since), gte(readingSessions.endProgress, 100), libraryFilter)) + .where(and(eq(readingSessions.userId, userId), gte(readingSessions.startedAt, since), gte(readingSessions.endProgress, 99), libraryFilter)) .groupBy(bookFiles.bookId) .as('completed_in_window');