Skip to content

Latest commit

 

History

202 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Libroo

Libroo is a private, physical-first library management system for home and small-library inventories. It tracks books, shelf locations, reading state, lending logistics, borrower links, and admin-managed access.

Current Stack

  • Nuxt 4, Nuxt UI v4, Pinia, and Nuxt Image.
  • Effect services and repositories for server-side business logic.
  • Drizzle ORM with SQLite-compatible storage.
  • Better Auth for accounts, sessions, roles, bans, password reset, and email verification.
  • NuxtHub on Cloudflare for hosted D1 database and R2/blob storage.
  • Self-hosted Docker profile with local libSQL/SQLite, local blob storage, and Sharp WebP cover conversion.

Database migrations live under server/db/migrations/sqlite.

Beta Release Features

  • Email/password authentication through Better Auth.
  • First-user admin promotion for empty installs.
  • Admin user management, role changes, bans, invites, and audit log.
  • Optional public registration or invite-only registration.
  • Manual book creation and ISBN lookup with Open Library metadata.
  • Library list/detail views, authors, locations, tags, ratings, notes, and reading progress.
  • Borrowing and lending workflows, including public borrower invite links.
  • Versioned CSV import/export with optional background metadata and cover enrichment.
  • Local and hosted cover/blob storage.
  • Optional SMTP or Plunk email delivery for verification, password reset, invites, and security notifications.
  • Self-hosted Docker deployment and hosted Cloudflare/NuxtHub deployment.

Run with Self-Hosted Docker

Run Libroo on your own machine or server with Docker and Docker Compose. Your library, uploads, and cover images are kept in a persistent Docker volume.

First install

Clone the repository and create .env. The following creates a new auth secret only when .env does not already exist, so it is safe to run again.

git clone https://github.com/niklhut/libroo.git
cd libroo

if [ -e .env ] || [ -L .env ]; then
  echo '.env already exists; leaving it unchanged.'
else
  umask 077
  secret="$(openssl rand -hex 32)"
  sed '/^NUXT_BETTER_AUTH_SECRET=/d' .env.example > .env
  printf '\nNUXT_BETTER_AUTH_SECRET=%s\n' "$secret" >> .env
fi

If people will access Libroo through a domain or reverse proxy, set NUXT_BETTER_AUTH_URL in .env to that public address before starting. The default http://localhost:3000 is suitable when running it on your own machine.

docker compose up -d

Open http://localhost:3000/register and create the first account. It becomes the administrator for the new library.

Keep a fixed version (optional)

Normal installs follow the newest published release. If you prefer to choose when to upgrade, set LIBROO_IMAGE in your untracked .env to a full release tag, such as:

LIBROO_IMAGE=ghcr.io/niklhut/libroo:0.5.0

Use an image digest instead when you need to run one exact image. See .env.example for the other available settings.

Build it yourself

If you are changing Libroo or want to run an image built from this checkout:

docker build -t libroo:local .
LIBROO_IMAGE=libroo:local docker compose up -d

You can instead set LIBROO_IMAGE=libroo:local in your untracked .env while developing. Do not change the repository's Compose file.

Updates and backups

Back up before upgrading. The libroo-data Docker volume contains the database at /data/db/sqlite.db and uploads at /data/blob.

Keep your existing .env; do not replace it with .env.example. In particular, keep NUXT_BETTER_AUTH_SECRET unchanged because changing it signs everyone out. To update to the newest published release:

docker compose pull --policy always
docker compose up -d

Libroo applies database migrations as it starts. Migrations move forward only unless a release provides a rollback plan.

You can also use the included backup and restore commands:

pnpm backup:selfhost -- --output-dir /backups/libroo
pnpm restore:selfhost -- /backups/libroo/libroo-selfhost-backup-YYYY-MM-DDTHH-MM-SSZ.tar.gz

See docs/backup-restore.md for self-hosted archives, hosted D1/R2 exports, manifests, verification, and retention guidance. The hosted-service backup retention target is 30 days before public launch.

Environment Configuration

Better Auth documentation refers to BETTER_AUTH_SECRET and BETTER_AUTH_URL. In this Nuxt app, set them with the Nuxt runtime-config prefix:

Variable Required Notes
NUXT_BETTER_AUTH_SECRET Production yes Stable secret used to sign Better Auth sessions. Generate with openssl rand -base64 32 and keep it unchanged across restarts.
NUXT_BETTER_AUTH_URL Production yes Public origin of the app, for example https://libroo.example.com or http://localhost:3000.
NUXT_LIBROO_RUNTIME_PROFILE Optional selfhost by default. Use cloudflare for NuxtHub/D1/R2 builds.
NUXT_DATABASE_URL Self-host libSQL/SQLite URL. Local default is file:.data/db/sqlite.db; Docker uses file:/data/db/sqlite.db.
NUXT_LOCAL_STORAGE_DIR Self-host Local blob directory. Local default is .data/blob; Docker uses /data/blob.
NUXT_PUBLIC_REGISTRATION_ENABLED Optional true by default. Controls new-user creation, not sign-in. Set false after bootstrap to require an invite for password signup and block new OIDC users.
NUXT_EMAIL_PASSWORD_ENABLED Optional true by default. Set false to disable local password and passkey authentication, password signup (including invite-backed signup), and password reset.
NUXT_PUBLIC_PASSKEYS_ENABLED Optional false by default. Enables passkey sign-in and enrollment on a secure origin while local authentication is enabled; passkeys do not create users.
NUXT_PUBLIC_OIDC_ENABLED Optional false by default. Enables the configured OIDC provider; see OAuth / OIDC Sign-In.
NUXT_OIDC_TRUST_PROVIDER Optional false by default. When true, matching-email OIDC accounts may be linked automatically for an existing locally verified user even without a provider email_verified claim. Use only with a fully trusted IdP.
NUXT_PUBLIC_OPEN_LIBRARY_LINKS_ENABLED Optional true in development and false in production unless explicitly set.

Registration and OIDC are independent switches. Passkeys are part of local authentication and additionally require NUXT_EMAIL_PASSWORD_ENABLED=true. After the first account has been created, the combinations behave as follows (assuming OIDC and passkeys are otherwise configured where mentioned):

Public registration Email/password New users Existing users
true true Public password signup and OIDC just-in-time creation are allowed. Password, passkey, and already-linked OIDC sign-in remain available.
false true Password signup requires a valid invite; OIDC cannot create a user. Password, passkey, and already-linked OIDC sign-in remain available.
true false OIDC can create users; password signup is unavailable even with an invite. Already-linked OIDC sign-in remains available; password and passkey sign-in are disabled.
false false No password or OIDC user creation; invites cannot create password accounts. Only already-linked OIDC sign-in remains available.

An empty installation has one bootstrap exception: its first account may be created by password or OIDC even when public registration is disabled, and that account is promoted to admin. NUXT_PUBLIC_OIDC_ENABLED only exposes the OIDC provider; it does not override the registration policy. Passkeys are an authentication method for existing users, not a signup method, and are disabled alongside local password authentication.

With NUXT_OIDC_TRUST_PROVIDER=false, an OIDC sign-in whose email already belongs to a Libroo user is not linked implicitly and returns account_not_linked; the provider must be linked explicitly from an authenticated account. Setting it to true makes the configured provider a trusted identity source: an account with the same verified local email is linked automatically even if the provider does not return email_verified=true. Better Auth still requires the existing Libroo user's emailVerified field to be true; disabling Libroo's email-verification feature does not mark existing users as verified. A signed-in user can instead connect the configured provider explicitly under Settings → Account & sign-in; this proves control of both accounts without requiring email verification. Connect OIDC before disabling the user's existing password sign-in method. Different-email accounts are never merged. Enable trust only when the IdP reliably verifies ownership of every email address it asserts.

For an existing installation, keep NUXT_PUBLIC_REGISTRATION_ENABLED=false and NUXT_EMAIL_PASSWORD_ENABLED=true during the OIDC transition. That keeps public password signup closed (invite-backed signup still works) while existing users sign in with their current method and connect OIDC under Settings → Account & sign-in. Once every user has linked and tested OIDC, local password and passkey authentication can be disabled without affecting those linked identities. See the OIDC rollout guide for the full flow.

Email is optional, but password reset, invite emails, security notifications, and verification emails require a provider.

SMTP is available in the self-hosted profile:

NUXT_EMAIL_PROVIDER=smtp
NUXT_EMAIL_FROM="Libroo <no-reply@your-libroo.example.com>"
NUXT_SMTP_HOST=smtp.example.com
NUXT_SMTP_PORT=587
NUXT_SMTP_SECURE=false
NUXT_SMTP_USER=your-smtp-user
NUXT_SMTP_PASSWORD=your-smtp-password

Plunk is available in both profiles and is the hosted Cloudflare profile's email provider:

NUXT_EMAIL_PROVIDER=plunk
NUXT_EMAIL_FROM=no-reply@your-libroo.example.com
NUXT_EMAIL_REPLY_TO=support@your-libroo.example.com
NUXT_PLUNK_API_KEY=sk_your_secret_key
NUXT_PLUNK_BASE_URL=https://next-api.useplunk.com

Set NUXT_EMAIL_VERIFICATION_ENABLED=true when users should verify email ownership before normal app access. When it is false or unset, registration and sign-in work without verification; email changes apply after current-password confirmation.

Database Migrations

The current baseline migration is server/db/migrations/sqlite/0000_initial_beta.sql. Fresh local and self-hosted installs should apply migrations to an empty SQLite database with:

pnpm exec node scripts/migrate-selfhost.mjs

The Docker image runs that script automatically before starting Nuxt. It creates the database directory, checks writable space, and applies migrations from server/db/migrations/sqlite.

Hosted Cloudflare deployments apply the same SQLite migration files to D1 after pnpm build:cloudflare:

pnpm build:cloudflare
pnpm exec wrangler d1 migrations apply DB --remote --config .output/server/wrangler.json

After the beta release, add new Drizzle migrations linearly. Do not rewrite existing migration history for installations with live data.

Local Development

For local development, install Node.js 22 or newer and pnpm 11.8.0, or enable Corepack for the package manager version in package.json.

git clone https://github.com/niklhut/libroo
cd libroo
pnpm install
cp .env.example .env
openssl rand -base64 32

Put the generated secret in .env:

NUXT_BETTER_AUTH_SECRET=<output from openssl rand -base64 32>
NUXT_BETTER_AUTH_URL=http://localhost:3000
NUXT_LIBROO_RUNTIME_PROFILE=selfhost
NUXT_DATABASE_URL=file:.data/db/sqlite.db
NUXT_LOCAL_STORAGE_DIR=.data/blob

Apply the SQLite baseline migration, then start Nuxt:

pnpm exec node scripts/migrate-selfhost.mjs
pnpm dev

Open http://localhost:3000/register and create the first account. The Better Auth policy plugin in server/utils/libroo-admin-auth-plugin.ts promotes the first created user in an empty database to admin.

Useful checks before shipping changes:

pnpm lint:fix
pnpm typecheck
pnpm test
pnpm test:unit
pnpm test:d1
pnpm test:integration
pnpm test:scripts
pnpm test:e2e
  • pnpm test runs all Vitest projects.
  • pnpm test:unit runs pure logic tests.
  • pnpm test:d1 runs Cloudflare D1/Miniflare-bound tests.
  • pnpm test:integration runs cross-service and booted-server flows.
  • pnpm test:scripts runs backup, restore, and migration script tests.
  • pnpm test:e2e runs Playwright browser flows and builds the self-host runtime first.

CI runs these same Vitest projects through a per-project matrix, so a passing pnpm test locally mirrors CI coverage. E2E runs separately because it depends on its own Playwright build/runtime; CI still reports each Vitest project's status individually.

The full first-release manual QA pass lives in docs/first-release-qa.md.

Hosted Cloudflare/NuxtHub

The hosted profile uses NuxtHub with Cloudflare D1 and R2-compatible blob storage. It requires Wrangler and Cloudflare credentials.

NUXT_LIBROO_RUNTIME_PROFILE=cloudflare pnpm build:cloudflare

Hosted deployment secrets and environment variables:

  • CLOUDFLARE_API_TOKEN
  • CLOUDFLARE_ACCOUNT_ID
  • NUXT_HUB_CLOUDFLARE_DATABASE_ID
  • NUXT_HUB_CLOUDFLARE_BUCKET_NAME
  • NUXT_BETTER_AUTH_SECRET
  • NUXT_BETTER_AUTH_URL
  • NUXT_PLUNK_API_KEY, when email delivery is enabled

Recommended hosted variables:

  • NUXT_EMAIL_PROVIDER=plunk
  • NUXT_EMAIL_FROM=no-reply@your-domain.example
  • NUXT_EMAIL_VERIFICATION_ENABLED=true
  • NUXT_PUBLIC_REGISTRATION_ENABLED=false, after the first admin account exists
  • NUXT_PUBLIC_OPEN_LIBRARY_LINKS_ENABLED=false

Deployments should apply D1 migrations immediately before wrangler deploy. The repository's deployment notes include the CI promotion policy, preview caveats, rollback expectations, and full variable tables in docs/deployment.md.

First Admin Setup

Start every new install with an empty database and public registration enabled:

NUXT_PUBLIC_REGISTRATION_ENABLED=true

Open /register and create the first account, or use the configured OIDC button. The librooAdminPolicyPlugin runs after Better Auth creates the user and atomically assigns user.role = 'admin' if no admin user exists. After confirming the account can access /admin/users, set NUXT_PUBLIC_REGISTRATION_ENABLED=false for private or hosted invite-only operation and restart/redeploy. The empty-database bootstrap is also permitted when registration is already disabled, but enabling it during setup makes the intended state clearer.

The policy also prevents common lockouts: admins cannot demote or ban themselves, and Libroo rejects demoting or banning the last active admin.

Operator Guide: Users And Auth

Libroo uses Better Auth as the source of truth for user authentication and account state.

  • Roles are stored on Better Auth's user.role field. Libroo treats any role token containing admin as admin.
  • Bans are stored on Better Auth's user.banned, user.ban_reason, and user.ban_expires fields.
  • There is no separate Libroo user-auth table. Do not create or update a parallel user table for auth state.
  • Manage users from /admin/users whenever possible. That UI uses Better Auth admin APIs and Libroo policy/audit plugins.
  • Admin invite links live in signup_invites and create normal Better Auth users when accepted.
  • Account deletion removes Better Auth records and the user's Libroo library data. See docs/account-deletion.md for retention and support operations.

Legal And Self-Hosting

Libroo is licensed under the GNU AGPLv3. You may use, modify, and self-host it. If you run a modified version as a network service for other users, the AGPL expects you to provide the corresponding source code for that modified version.

The app does not ship legally sufficient privacy-policy or imprint text. Operators can configure public legal URLs or Markdown sources with:

NUXT_PUBLIC_LEGAL_PRIVACY_POLICY_URL=
NUXT_PUBLIC_LEGAL_IMPRINT_URL=
NUXT_LEGAL_PRIVACY_POLICY_MARKDOWN_URL=
NUXT_LEGAL_IMPRINT_MARKDOWN_URL=

Roadmap

Available in the beta release:

  • Core private-library catalog, locations, reading progress, ratings, notes, and tags.
  • ISBN lookup and manual entry.
  • Lending, borrower links, and loan return/cancel flows.
  • Better Auth-backed registration, login, password reset, email verification, first-admin promotion, invites, role management, bans, and audit log.
  • SQLite self-hosting and Cloudflare/NuxtHub hosted deployment.
  • CSV import/export and account deletion.
  • Backup/restore tooling for self-hosted operators and documented hosted D1/R2 backup procedures.

Future work toward v1:

  • Richer shelf mapping and physical inventory workflows.
  • Reminders and notification scheduling for loans.
  • Barcode scanner polish and bulk inventory flows.
  • More import sources and metadata reconciliation tools.
  • Hardened preview environments for Cloudflare deployments.

More Documentation

About

Libroo is a personal library tracker built to help you manage your books, collections, and track who borrowed what.

Resources

Stars

9 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages