Deploying BookLogr
BookLogr is a simple, self-hosted service designed to help you manage your personal book library with complete control over your data. Built with React frontend and Python Flask backend, BookLogr provides an elegant solution for tracking books you own, read, or want to read. Whether you’re an avid reader maintaining a personal catalog, a book collector organizing your collection, or someone wanting to share their reading journey with friends and family, BookLogr offers the privacy and flexibility of self-hosted infrastructure combined with powerful library management features.
Why BookLogr?
BookLogr stands out as the premier choice for self-hosted personal library management with exceptional features:
- Self-Hosted Control: Complete control over your data with self-hosted infrastructure
- Book Lookup: Easily search and add books by title or ISBN powered by OpenLibrary
- Reading Lists: Organize books into predefined lists (Reading, Already Read, To Be Read)
- Progress Tracking: Track your current page in books you’re actively reading
- Book Ratings: Rate books on a 0.5 to 5-star scale
- Notes & Quotes: Save short notes and meaningful quotes from books you read
- Public Library Sharing: Optional public profile to share your library with friends and family
- Mastodon Integration: Automatically share reading progress to Mastodon
- Data Export: Export your complete library in multiple formats (CSV, JSON, HTML)
- Database Flexibility: Choose between SQLite (default) or PostgreSQL
- Multi-Language Support: Available in multiple languages including Simplified Chinese
- Privacy First: Your reading data stays on your own server
- Responsive Design: Works seamlessly on desktop and mobile devices
- Open Source: Apache 2.0 licensed with active community development
- Modern Tech Stack: React frontend with Python Flask backend
- Book Metadata: Automatically fetch and display book information
- Search Functionality: Full-text search through your personal library
- Advanced Filtering: Filter by reading status, ratings, and dates
- Reading Statistics: Track reading habits and book statistics
- User Management: Support for multiple user accounts
- API Available: RESTful API for integrations
BookLogr is ideal for personal book collectors managing their reading journey, avid readers tracking literary history, book clubs organizing shared reading, users prioritizing privacy over cloud services, and those wanting complete data ownership. With persistent storage on Klutch.sh, your personal library remains secure and always accessible.
Prerequisites
Before deploying BookLogr, ensure you have:
- A Klutch.sh account
- A GitHub repository with your BookLogr deployment configuration
- Basic familiarity with Docker and Git
- Sufficient storage for your book library metadata (typically 5-50GB depending on library size)
- Database knowledge (SQLite or PostgreSQL)
- Understanding of Python Flask applications
- A custom domain for your personal library (recommended)
Important Considerations
Deploying BookLogr
Create a New Project
Log in to your Klutch.sh dashboard and create a new project for your BookLogr instance.
Prepare Your Repository
Create a GitHub repository with the following structure for your BookLogr deployment:
booklogr-deploy/├─ Dockerfile├─ .env.example├─ docker-entrypoint.sh├─ .gitignore└─ README.mdHere’s a Dockerfile for BookLogr:
FROM node:18-alpine AS frontend-builderWORKDIR /app/web# Clone and build frontendRUN apk add --no-cache git && \git clone https://github.com/Mozzo1000/booklogr.git /tmp/booklogr && \cp -r /tmp/booklogr/web/* . && \npm ci && \npm run buildFROM python:3.11-slimWORKDIR /app# Install system dependenciesRUN apt-get update && apt-get install -y \curl \git \postgresql-client \&& rm -rf /var/lib/apt/lists/*# Clone BookLogr repositoryRUN git clone https://github.com/Mozzo1000/booklogr.git /tmp/booklogr && \cp -r /tmp/booklogr/* .# Install Python dependenciesRUN pip install --no-cache-dir poetry && \poetry config virtualenvs.create false && \poetry install --no-dev# Copy built frontend from builderCOPY --from=frontend-builder /app/web/build ./web/build# Create necessary directoriesRUN mkdir -p /app/data \/app/logs \/app/instance && \chmod -R 755 /app# Copy entrypoint scriptCOPY docker-entrypoint.sh /RUN chmod +x /docker-entrypoint.sh# Expose portEXPOSE 5000# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \CMD curl -f http://localhost:5000/ || exit 1# Run entrypointENTRYPOINT ["/docker-entrypoint.sh"]CMD ["python", "-m", "flask", "run", "--host=0.0.0.0"]Create a
docker-entrypoint.shfile:#!/bin/bashset -e# Create necessary directoriesmkdir -p /app/data \/app/logs \/app/instance# Set proper permissionschmod -R 755 /appecho "Initializing BookLogr..."# Wait for database (if using PostgreSQL)if [ "$DATABASE_URL" != "${DATABASE_URL#postgresql}" ]; thenecho "Waiting for PostgreSQL..."until PGPASSWORD=$DATABASE_PASSWORD psql -h $DATABASE_HOST -U $DATABASE_USER -d postgres -c "\q" 2>/dev/null; doecho "PostgreSQL is unavailable - sleeping..."sleep 2doneecho "PostgreSQL is up!"fi# Initialize databaseecho "Running database migrations..."python -m flask db upgrade || true# Create default user if specifiedif [ -n "$AUTH_DEFAULT_USER" ] && [ -n "$AUTH_DEFAULT_PASSWORD" ]; thenecho "Setting up default user..."python << 'EOF'import osfrom app import create_appfrom app.models import Userapp = create_app()with app.app_context():user = User.query.filter_by(username=os.getenv('AUTH_DEFAULT_USER')).first()if not user:user = User(username=os.getenv('AUTH_DEFAULT_USER'))user.set_password(os.getenv('AUTH_DEFAULT_PASSWORD'))from app import dbdb.session.add(user)db.session.commit()print(f"Created user: {os.getenv('AUTH_DEFAULT_USER')}")else:print(f"User {os.getenv('AUTH_DEFAULT_USER')} already exists")EOFfiecho "BookLogr is starting..."exec "$@"Create a
.env.examplefile:Terminal window # BookLogr ConfigurationFLASK_APP=app.pyFLASK_ENV=productionSECRET_KEY=change-me-to-a-secret-key-32-characters# Database Configuration (SQLite default)DATABASE_URL=sqlite:////app/data/booklogr.db# OR PostgreSQL Configuration# DATABASE_URL=postgresql://user:password@database.internal:5432/booklogr# DATABASE_PASSWORD=secure_password# Server ConfigurationSERVER_NAME=booklogr.yourdomain.comPREFERRED_URL_SCHEME=https# AuthenticationAUTH_DEFAULT_USER=adminAUTH_DEFAULT_PASSWORD=secure_passwordAUTH_TYPE=local# Application SettingsTITLE=BookLogrLANGUAGE=enTIMEZONE=UTC# OpenLibrary APIOPENLIBRARY_BASE_URL=https://openlibrary.org# Email Configuration (optional)MAIL_SERVER=smtp.gmail.comMAIL_PORT=587MAIL_USE_TLS=trueMAIL_USERNAME=your-email@gmail.comMAIL_PASSWORD=app-password# Mastodon Integration (optional)MASTODON_ENABLED=falseMASTODON_INSTANCE=mastodon.socialMASTODON_ACCESS_TOKEN=your-access-token# LoggingLOG_LEVEL=INFOLOG_FILE=/app/logs/booklogr.log# FeaturesALLOW_PUBLIC_PROFILE=trueALLOW_USER_REGISTRATION=falseALLOW_DATA_EXPORT=trueCreate a
.gitignorefile:.env__pycache__/*.pyc.venv/data/logs/instance/build/dist/*.egg-info/node_modules/.DS_Store*.db*.sqlite.idea/Commit and push to your GitHub repository:
Terminal window git initgit add .git commit -m "Initial BookLogr deployment"git remote add origin https://github.com/yourusername/booklogr-deploy.gitgit push -u origin mainCreate a New App
In the Klutch.sh dashboard:
- Click “Create New App”
- Select your GitHub repository containing the Dockerfile
- Choose the branch (typically
mainormaster) - Klutch.sh will automatically detect the Dockerfile in the root directory
Configure Environment Variables
Set up these essential environment variables in your Klutch.sh dashboard:
Variable Description Example FLASK_APPFlask application entry app.pyFLASK_ENVEnvironment mode productionSECRET_KEYSession encryption key your-secret-key-32-charsDATABASE_URLDatabase connection string sqlite:////app/data/booklogr.dbSERVER_NAMEYour domain booklogr.yourdomain.comAUTH_DEFAULT_USERAdmin username adminAUTH_DEFAULT_PASSWORDAdmin password secure_passwordTITLEApplication title BookLogrLANGUAGEDefault language enTIMEZONEServer timezone UTCALLOW_PUBLIC_PROFILEPublic library sharing trueALLOW_DATA_EXPORTEnable data export trueLOG_LEVELLogging verbosity INFOConfigure Persistent Storage
BookLogr requires persistent storage for database and library data. Add persistent volumes:
Mount Path Description Recommended Size /app/dataSQLite database and application data 50GB /app/logsApplication logs 10GB /app/instanceInstance-specific files 5GB In the Klutch.sh dashboard:
- Navigate to your app settings
- Go to the “Volumes” section
- Click “Add Volume” for each mount path
- Set mount paths and sizes as specified above
- Ensure data volume is sized for your library size
Set Network Configuration
Configure your app’s network settings:
- Select traffic type: HTTP
- Recommended internal port: 5000 (Flask default development port)
- Klutch.sh will handle HTTPS termination via reverse proxy
- The application will be accessible at your configured domain
Configure Custom Domain
BookLogr works best with a custom domain for personal library access:
- Navigate to your app’s “Domains” section in Klutch.sh
- Click “Add Custom Domain”
- Enter your domain (e.g.,
library.yourdomain.comorbooklogr.yourdomain.com) - Configure DNS with a CNAME record pointing to your Klutch.sh app
- Update
SERVER_NAMEenvironment variable to match your domain - Klutch.sh will automatically provision SSL certificates
- Access your library at
https://library.yourdomain.com
Deploy Your App
- Review all settings and environment variables carefully
- Verify all persistent volumes are properly configured with adequate sizes
- Ensure database connection string matches your configuration
- Click “Deploy”
- Klutch.sh will build the Docker image and start your BookLogr instance
- Wait for the deployment to complete (typically 10-15 minutes)
- Access your BookLogr instance at your configured domain
- Log in with admin credentials to begin building your library
Initial Setup and Configuration
After deployment completes, access your BookLogr instance to set up your personal library.
Accessing BookLogr
Navigate to your domain: https://library.yourdomain.com
Log in with the admin credentials you configured during setup.
Dashboard Overview
Explore the main interface:
- My Library: View all books in your library
- Reading: Books currently being read
- Already Read: Completed books with ratings
- To Be Read: Books on your wishlist
- Settings: Configure application preferences
- Profile: Manage your account and public profile
Adding Books to Your Library
Build your library with ease:
- Click “Add Book” button
- Search by title, author, or ISBN
- Select book from search results
- Confirm book details (title, author, cover)
- Assign to reading list (Reading, Already Read, To Be Read)
- Click “Add to Library”
Alternatively, scan book ISBN with mobile device:
- Use mobile app or browser camera
- Scan book barcode
- Automatically adds book to library
- Assign reading list
Organizing Your Library
Manage your books effectively:
-
Reading List Management:
- Move books between lists by updating status
- Track books you’re currently reading
- Mark completed books
- Create wishlist of books to read
-
Book Details:
- View book metadata (cover, synopsis, author)
- Add personal ratings (0.5-5 stars)
- Write notes about the book
- Save meaningful quotes
- Record reading progress (current page)
-
Filtering and Search:
- Search by title, author, or genre
- Filter by reading status
- Filter by rating
- Sort by date added or title
- Advanced search capabilities
Tracking Reading Progress
Monitor your reading journey:
- For each book, set current page number
- Update progress as you read
- System tracks reading start date
- View completion percentage
- Get reading statistics
Rating and Reviewing Books
Document your reading experience:
- Open book details
- Set rating from 0.5 to 5 stars
- Write personal notes about the book
- Save meaningful quotes from reading
- Add completion date
- View all ratings and notes
Managing Your Profile
Configure your public presence:
- Navigate to “Settings” → “Profile”
- Set profile visibility (private/public)
- Customize display name
- Configure public library sharing
- Set profile description
- Manage profile picture
Public Library Sharing
Share your reading journey:
- Enable “Allow Public Profile” in settings
- Configure public library visibility
- Choose what information to display:
- Book list and ratings
- Reading statistics
- Currently reading
- Completed books
- Get shareable link to your library
- Users can view without login
Configuring Email Settings
Set up email notifications (optional):
- Navigate to “Settings” → “Email”
- Configure SMTP server settings:
- SMTP server address
- SMTP port
- Email authentication
- Set notification preferences
- Test email configuration
- Enable/disable notifications
Setting Up Mastodon Integration
Share reading progress automatically:
- Go to “Settings” → “Mastodon Integration”
- Enable Mastodon sharing
- Enter Mastodon instance URL
- Generate and paste access token
- Configure what to share:
- Book completions
- Reading progress
- Book ratings
- Posts automatically share to Mastodon
Exporting Your Data
Backup or migrate your library:
- Navigate to “Settings” → “Data Export”
- Choose export format:
- CSV: Spreadsheet format for spreadsheet applications
- JSON: Complete data export for backups
- HTML: Viewable library webpage
- Click “Export”
- Download exported file
- Store backup in safe location
Database Configuration
For PostgreSQL deployments:
- Update
DATABASE_URLenvironment variable - Format:
postgresql://user:password@host:port/database - Run database migrations
- Verify connection successful
- Monitor database performance
Environment Variable Examples
Basic SQLite Configuration
FLASK_APP=app.pyFLASK_ENV=productionSECRET_KEY=abc123def456ghi789jkl012mno345pqrDATABASE_URL=sqlite:////app/data/booklogr.dbSERVER_NAME=library.yourdomain.comAUTH_DEFAULT_USER=adminAUTH_DEFAULT_PASSWORD=secure_passwordALLOW_PUBLIC_PROFILE=trueComplete PostgreSQL Production Configuration
# ApplicationFLASK_APP=app.pyFLASK_ENV=productionSECRET_KEY=very_secure_secret_key_32_characters_longDEBUG=false
# Database - PostgreSQLDATABASE_URL=postgresql://booklogr:secure_db_password@postgres.internal:5432/booklogrDATABASE_PASSWORD=secure_db_passwordDATABASE_USER=booklogrDATABASE_HOST=postgres.internalDATABASE_PORT=5432DATABASE_NAME=booklogr
# Server ConfigurationSERVER_NAME=library.yourdomain.comPREFERRED_URL_SCHEME=httpsWEB_CONCURRENCY=4
# AuthenticationAUTH_DEFAULT_USER=adminAUTH_DEFAULT_PASSWORD=very_secure_admin_password_32_charsAUTH_TYPE=localSESSION_TIMEOUT=2592000
# Application SettingsTITLE=My Personal LibraryLANGUAGE=enTIMEZONE=America/New_YorkDEFAULT_PAGE_SIZE=20
# OpenLibrary ConfigurationOPENLIBRARY_BASE_URL=https://openlibrary.orgOPENLIBRARY_TIMEOUT=10CACHE_BOOK_DATA=trueCACHE_TTL=86400
# Email ConfigurationMAIL_SERVER=smtp.gmail.comMAIL_PORT=587MAIL_USE_TLS=trueMAIL_USERNAME=noreply@yourdomain.comMAIL_PASSWORD=app_specific_passwordMAIL_DEFAULT_SENDER=BookLogr <noreply@yourdomain.com>
# Mastodon IntegrationMASTODON_ENABLED=trueMASTODON_INSTANCE=mastodon.socialMASTODON_ACCESS_TOKEN=your_mastodon_access_tokenMASTODON_POST_ON_COMPLETION=true
# FeaturesALLOW_PUBLIC_PROFILE=trueALLOW_USER_REGISTRATION=falseALLOW_DATA_EXPORT=trueALLOW_MULTIPLE_USERS=false
# LoggingLOG_LEVEL=INFOLOG_FILE=/app/logs/booklogr.logLOG_MAX_BYTES=10485760LOG_BACKUP_COUNT=10LOG_FORMAT=%(asctime)s - %(name)s - %(levelname)s - %(message)s
# PerformanceSQLALCHEMY_ECHO=falseSQLALCHEMY_POOL_SIZE=10SQLALCHEMY_POOL_RECYCLE=3600SQLALCHEMY_TRACK_MODIFICATIONS=false
# SecurityPREFERRED_URL_SCHEME=httpsSESSION_COOKIE_SECURE=trueSESSION_COOKIE_HTTPONLY=trueSESSION_COOKIE_SAMESITE=LaxSample Code and Getting Started
Python - Book Management Integration
# BookLogr Book Management Integration
from app import create_app, dbfrom app.models import Book, User, Rating, Notefrom sqlalchemy import funcfrom datetime import datetime
class BookLibrary: def __init__(self, app=None): self.app = app or create_app() self.db = db
def add_book(self, user_id, book_data): """Add a book to user's library""" book = Book( user_id=user_id, title=book_data.get('title'), author=book_data.get('author'), isbn=book_data.get('isbn'), pages=book_data.get('pages'), cover_url=book_data.get('cover_url'), publication_date=book_data.get('publication_date'), description=book_data.get('description'), status=book_data.get('status', 'to_be_read'), date_added=datetime.now() )
self.db.session.add(book) self.db.session.commit()
return book.id
def get_user_library(self, user_id, status=None): """Get all books in user's library, optionally filtered by status""" query = Book.query.filter_by(user_id=user_id)
if status: query = query.filter_by(status=status)
return query.order_by(Book.date_added.desc()).all()
def search_books(self, user_id, query_string): """Search user's library by title, author, or isbn""" search_term = f"%{query_string}%"
results = Book.query.filter_by(user_id=user_id).filter( (Book.title.ilike(search_term)) | (Book.author.ilike(search_term)) | (Book.isbn.ilike(search_term)) ).all()
return results
def rate_book(self, user_id, book_id, rating_value): """Add or update book rating""" if not 0.5 <= rating_value <= 5.0: raise ValueError("Rating must be between 0.5 and 5.0")
rating = Rating.query.filter_by( user_id=user_id, book_id=book_id ).first()
if rating: rating.value = rating_value rating.date_rated = datetime.now() else: rating = Rating( user_id=user_id, book_id=book_id, value=rating_value, date_rated=datetime.now() ) self.db.session.add(rating)
self.db.session.commit() return rating
def add_note(self, user_id, book_id, note_text, page_number=None): """Add a note or quote to a book""" note = Note( user_id=user_id, book_id=book_id, text=note_text, page_number=page_number, date_created=datetime.now() )
self.db.session.add(note) self.db.session.commit()
return note.id
def update_reading_progress(self, user_id, book_id, current_page): """Update current page in book being read""" book = Book.query.filter_by( id=book_id, user_id=user_id ).first()
if not book: raise ValueError("Book not found")
book.current_page = current_page book.last_read = datetime.now()
self.db.session.commit()
return { 'book_id': book.id, 'current_page': book.current_page, 'progress_percent': (current_page / book.pages) * 100 if book.pages else 0 }
def get_reading_statistics(self, user_id): """Get reading statistics for user""" books = Book.query.filter_by(user_id=user_id).all()
total_books = len(books) completed_books = len([b for b in books if b.status == 'read']) reading_books = len([b for b in books if b.status == 'reading']) to_read_books = len([b for b in books if b.status == 'to_be_read'])
avg_rating = self.db.session.query(func.avg(Rating.value)).filter( Rating.user_id == user_id ).scalar() or 0
total_pages = sum([b.pages or 0 for b in completed_books])
return { 'total_books': total_books, 'completed_books': completed_books, 'currently_reading': reading_books, 'to_be_read': to_read_books, 'average_rating': round(avg_rating, 1), 'total_pages_read': total_pages }
def export_library_json(self, user_id): """Export entire library as JSON""" books = Book.query.filter_by(user_id=user_id).all()
export_data = { 'user_id': user_id, 'export_date': datetime.now().isoformat(), 'books': [] }
for book in books: book_data = { 'id': book.id, 'title': book.title, 'author': book.author, 'isbn': book.isbn, 'pages': book.pages, 'status': book.status, 'date_added': book.date_added.isoformat() }
# Add rating if exists rating = Rating.query.filter_by( user_id=user_id, book_id=book.id ).first() if rating: book_data['rating'] = rating.value
# Add notes notes = Note.query.filter_by( user_id=user_id, book_id=book.id ).all() book_data['notes'] = [ {'text': n.text, 'page': n.page_number} for n in notes ]
export_data['books'].append(book_data)
return export_data
# Usage examplewith app.app_context(): library = BookLibrary()
# Add a book book_id = library.add_book(1, { 'title': 'The Great Gatsby', 'author': 'F. Scott Fitzgerald', 'isbn': '978-0743273565', 'pages': 180, 'status': 'reading' })
# Rate the book library.rate_book(1, book_id, 4.5)
# Add notes library.add_note(1, book_id, "Beautiful prose about the Jazz Age", page_number=45)
# Update reading progress progress = library.update_reading_progress(1, book_id, 75)
# Get statistics stats = library.get_reading_statistics(1)JavaScript - Library Dashboard
// BookLogr Library Dashboard
class LibraryDashboard { constructor(containerId, apiBaseUrl) { this.container = document.getElementById(containerId); this.apiBaseUrl = apiBaseUrl; this.currentView = 'all'; this.books = []; this.init(); }
async init() { this.setupUI(); this.setupEventListeners(); await this.loadLibrary(); }
setupUI() { this.container.innerHTML = ` <div class="library-dashboard"> <div class="library-header"> <h1>My Personal Library</h1> <button id="addBookBtn" class="btn-primary">+ Add Book</button> </div>
<div class="library-stats"> <div class="stat-card"> <div class="stat-number" id="totalBooks">0</div> <div class="stat-label">Total Books</div> </div> <div class="stat-card"> <div class="stat-number" id="completedBooks">0</div> <div class="stat-label">Completed</div> </div> <div class="stat-card"> <div class="stat-number" id="currentlyReading">0</div> <div class="stat-label">Currently Reading</div> </div> <div class="stat-card"> <div class="stat-number" id="avgRating">0</div> <div class="stat-label">Avg Rating</div> </div> </div>
<div class="library-filters"> <button class="filter-btn active" data-filter="all">All Books</button> <button class="filter-btn" data-filter="reading">Reading</button> <button class="filter-btn" data-filter="read">Already Read</button> <button class="filter-btn" data-filter="to_be_read">To Be Read</button> </div>
<div class="library-search"> <input type="text" id="searchInput" placeholder="Search by title, author, or ISBN..." /> </div>
<div id="bookGrid" class="book-grid"></div> </div> `;
this.bookGrid = document.getElementById('bookGrid'); }
setupEventListeners() { document.getElementById('addBookBtn').addEventListener('click', () => { this.openAddBookDialog(); });
document.querySelectorAll('.filter-btn').forEach(btn => { btn.addEventListener('click', (e) => { document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); e.target.classList.add('active'); this.currentView = e.target.dataset.filter; this.displayBooks(); }); });
document.getElementById('searchInput').addEventListener('input', (e) => { this.searchBooks(e.target.value); }); }
async loadLibrary() { try { const response = await fetch(this.apiBaseUrl + '/api/library'); const data = await response.json();
this.books = data.books; this.updateStats(data.statistics); this.displayBooks(); } catch (error) { console.error('Failed to load library:', error); } }
displayBooks() { let filteredBooks = this.books;
if (this.currentView !== 'all') { filteredBooks = this.books.filter(b => b.status === this.currentView); }
if (filteredBooks.length === 0) { this.bookGrid.innerHTML = '<p class="no-books">No books found</p>'; return; }
this.bookGrid.innerHTML = filteredBooks.map(book => ` <div class="book-card" data-book-id="${book.id}"> <div class="book-cover"> ${book.cover_url ? `<img src="${book.cover_url}" alt="${book.title}" />` : '<div class="no-cover">No Cover</div>'} </div> <div class="book-info"> <h3 class="book-title">${this.escapeHtml(book.title)}</h3> <p class="book-author">by ${this.escapeHtml(book.author)}</p> ${book.rating ? `<div class="book-rating">${this.renderStars(book.rating)}</div>` : ''} <div class="book-progress"> ${book.status === 'reading' ? ` <div class="progress-bar"> <div class="progress-fill" style="width: ${(book.current_page / book.pages) * 100}%"></div> </div> <span class="progress-text">${book.current_page}/${book.pages} pages</span> ` : ''} </div> <div class="book-actions"> <button class="btn-small" onclick="dashboard.openBookDetails(${book.id})">Details</button> <button class="btn-small" onclick="dashboard.removeBook(${book.id})">Remove</button> </div> </div> </div> `).join(''); }
updateStats(stats) { document.getElementById('totalBooks').textContent = stats.total_books; document.getElementById('completedBooks').textContent = stats.completed_books; document.getElementById('currentlyReading').textContent = stats.currently_reading; document.getElementById('avgRating').textContent = stats.average_rating.toFixed(1); }
renderStars(rating) { const fullStars = Math.floor(rating); const hasHalf = rating % 1 !== 0; let stars = '★'.repeat(fullStars); if (hasHalf) stars += '⭐'; stars += '☆'.repeat(5 - fullStars - (hasHalf ? 1 : 0)); return stars; }
searchBooks(query) { if (!query) { this.displayBooks(); return; }
const filtered = this.books.filter(book => book.title.toLowerCase().includes(query.toLowerCase()) || book.author.toLowerCase().includes(query.toLowerCase()) || (book.isbn && book.isbn.includes(query)) );
this.bookGrid.innerHTML = filtered.map(book => ` <div class="book-card" data-book-id="${book.id}"> <div class="book-cover"> ${book.cover_url ? `<img src="${book.cover_url}" alt="${book.title}" />` : '<div class="no-cover">No Cover</div>'} </div> <h3>${this.escapeHtml(book.title)}</h3> <p>${this.escapeHtml(book.author)}</p> </div> `).join(''); }
openAddBookDialog() { // Implementation for add book dialog console.log('Opening add book dialog...'); }
async openBookDetails(bookId) { // Implementation for book details console.log('Opening book details for:', bookId); }
async removeBook(bookId) { if (confirm('Are you sure you want to remove this book?')) { try { await fetch(this.apiBaseUrl + '/api/books/' + bookId, { method: 'DELETE' }); await this.loadLibrary(); } catch (error) { console.error('Failed to remove book:', error); } } }
escapeHtml(text) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, m => map[m]); }}
// Initialize dashboardconst dashboard = new LibraryDashboard( 'libraryContainer', 'https://library.yourdomain.com');Bash - Backup and Maintenance Script
#!/bin/bash
# BookLogr Backup and Maintenance ScriptBACKUP_DIR="/backups/booklogr"TIMESTAMP=$(date +%Y%m%d_%H%M%S)RETENTION_DAYS=30DATABASE_PATH="/app/data/booklogr.db"
# Create backup directorymkdir -p $BACKUP_DIR
echo "Starting BookLogr backup..."
# Backup SQLite databaseif [ -f "$DATABASE_PATH" ]; then echo "Backing up SQLite database..." cp "$DATABASE_PATH" $BACKUP_DIR/booklogr_db_$TIMESTAMP.db gzip $BACKUP_DIR/booklogr_db_$TIMESTAMP.dbfi
# Backup application dataecho "Backing up application data..."tar -czf $BACKUP_DIR/booklogr_data_$TIMESTAMP.tar.gz \ /app/data \ /app/instance \ --exclude='/app/data/*.db-journal' \ 2>/dev/null || true
# Backup logsecho "Backing up logs..."tar -czf $BACKUP_DIR/booklogr_logs_$TIMESTAMP.tar.gz \ /app/logs 2>/dev/null || true
# Cleanup old backupsecho "Cleaning up old backups..."find $BACKUP_DIR -name "booklogr_*" -mtime +$RETENTION_DAYS -delete
# Verify backupsecho "Verifying backups..."for file in $BACKUP_DIR/*_$TIMESTAMP.*; do if [ -f "$file" ]; then size=$(du -h "$file" | awk '{print $1}') echo "✓ Created: $(basename $file) ($size)" fidone
# Calculate total backup sizeTOTAL_SIZE=$(du -sh $BACKUP_DIR | awk '{print $1}')
echo ""echo "Backup completed at: $TIMESTAMP"echo "Total backup size: $TOTAL_SIZE"echo "Backup location: $BACKUP_DIR"
# Database optimization (for SQLite)if [ -f "$DATABASE_PATH" ]; then echo "" echo "Optimizing SQLite database..." sqlite3 "$DATABASE_PATH" "VACUUM;" sqlite3 "$DATABASE_PATH" "ANALYZE;"fi
# Optional: Upload to cloud storage# aws s3 sync $BACKUP_DIR s3://your-bucket/booklogr-backups/ \# --exclude "*" --include "booklogr_*_$TIMESTAMP.*" \# --delete
# Optional: Send backup notification# curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \# -d "{\"text\":\"BookLogr backup completed: $(du -sh $BACKUP_DIR | awk '{print $1}')\"}"cURL - API Integration Examples
# BookLogr API Examples
# Get all books in librarycurl -X GET https://library.yourdomain.com/api/library \ -H "Content-Type: application/json"
# Get books by statuscurl -X GET "https://library.yourdomain.com/api/library?status=reading" \ -H "Content-Type: application/json"
# Search bookscurl -X GET "https://library.yourdomain.com/api/library/search?query=great+gatsby" \ -H "Content-Type: application/json"
# Get book detailscurl -X GET https://library.yourdomain.com/api/books/123 \ -H "Content-Type: application/json"
# Add book to librarycurl -X POST https://library.yourdomain.com/api/books \ -H "Content-Type: application/json" \ -d '{ "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "isbn": "978-0743273565", "pages": 180, "status": "reading" }'
# Rate a bookcurl -X POST https://library.yourdomain.com/api/books/123/rating \ -H "Content-Type: application/json" \ -d '{ "rating": 4.5 }'
# Add note to bookcurl -X POST https://library.yourdomain.com/api/books/123/notes \ -H "Content-Type: application/json" \ -d '{ "text": "Beautiful prose about the Jazz Age", "page_number": 45 }'
# Update reading progresscurl -X PUT https://library.yourdomain.com/api/books/123/progress \ -H "Content-Type: application/json" \ -d '{ "current_page": 85 }'
# Get reading statisticscurl -X GET https://library.yourdomain.com/api/statistics \ -H "Content-Type: application/json"
# Export librarycurl -X GET "https://library.yourdomain.com/api/export?format=json" \ -H "Content-Type: application/json" \ -o library_backup.json
# Get public profilecurl -X GET https://library.yourdomain.com/api/profile/public \ -H "Content-Type: application/json"
# Delete book from librarycurl -X DELETE https://library.yourdomain.com/api/books/123 \ -H "Content-Type: application/json"Library Management
Building Your Book Collection
Effective library organization:
-
Regular Addition:
- Add books as you acquire them
- Use ISBN scan for quick addition
- Import from wish lists
- Manually add rare or hard-to-find books
-
Reading Organization:
- Categorize by reading status
- Set reading goals
- Track multiple books simultaneously
- Maintain wishlist for future reading
-
Library Curation:
- Remove completed reads to archive
- Update reading progress regularly
- Rate completed books
- Document meaningful passages
Sharing Your Library
Share your reading journey:
-
Public Profile:
- Enable public library sharing
- Share unique profile link
- Friends can view your books
- No account required for viewers
-
Social Sharing:
- Share individual books
- Post reading achievements
- Share favorite quotes
- Mastodon integration for broader sharing
-
Data Privacy:
- Control what’s publicly visible
- Keep personal notes private
- Manage access levels
- Archive sensitive entries
Troubleshooting
Common Issues and Solutions
Issue: Cannot add books
Solutions:
- Verify OpenLibrary API is accessible
- Check ISBN format validity
- Try searching by title instead of ISBN
- Verify internet connection
- Check API rate limiting
Issue: Reading progress not saving
Troubleshooting:
- Verify database connectivity
- Check disk space availability
- Clear browser cache
- Restart application
- Review error logs
Issue: Slow library loading
Solutions:
- Optimize database (VACUUM, ANALYZE)
- Clear old logs
- Reduce library size
- Enable database indexing
- Check server resources
Issue: Export not working
Troubleshooting:
- Verify sufficient disk space
- Check database integrity
- Try different export format
- Review application logs
- Test file permissions
Issue: Mastodon integration failing
Solutions:
- Verify Mastodon instance URL
- Check access token validity
- Ensure network connectivity
- Review Mastodon API limits
- Check application settings
Updating BookLogr
To update BookLogr to a newer version:
- Backup your database and data files
- Update your Dockerfile to latest version
- Commit and push to GitHub
- Klutch.sh will automatically rebuild
- Test all functionality after update
- Verify library data integrity
- Monitor logs for any issues
Use Cases
Personal Book Tracking
- Track reading throughout the year
- Maintain reading statistics
- Rate and review books
- Share favorite reads with friends
Book Club Management
- Organize club book selections
- Track member progress
- Share notes and discussions
- Plan club meetings
Reading Goals
- Set annual reading targets
- Monitor reading progress
- Achieve reading milestones
- Build reading habits
Rare Book Collection
- Catalog valuable collection
- Document book conditions
- Share collection digitally
- Preserve collection history
Family Library
- Organize household books
- Track borrowed books
- Maintain reading history
- Share collection with family
Additional Resources
- BookLogr GitHub Repository - Source code and latest releases
- BookLogr Official Documentation - Comprehensive guides and tutorials
- BookLogr Live Demo - Try before deploying
- OpenLibrary API - Book metadata source
- BookLogr Wiki - Development and setup guides
- Klutch.sh Getting Started Guide
- Klutch.sh Volumes Documentation
- Klutch.sh Custom Domains Guide
Conclusion
Deploying BookLogr on Klutch.sh provides you with a powerful, self-hosted service to manage your personal book library with complete data control. With easy book lookup, reading progress tracking, rating and reviewing capabilities, public library sharing, data export options, and seamless Mastodon integration, BookLogr enables you to curate and share your reading journey. Klutch.sh’s managed infrastructure ensures your library is always available, secure, and performant, allowing you to focus on enjoying books and building your literary collection.
Start managing your personal library today by deploying BookLogr on Klutch.sh and experience the freedom of owning your reading data.