Triển khai Audiobookshelf trên Docker: Máy chủ sách nói Self-Hosted với tính năng đồng bộ tiến trình đa nền tảng

HomeLab tutorial - IT technology blog
HomeLab tutorial - IT technology blog

So sánh các giải pháp: Máy chủ sách nói chuyên dụng và hệ thống media đa năng

Hãy thử hình dung tình huống này: bạn đang nghe dở đến tiếng thứ 12 của một bộ tiểu thuyết giả tưởng dài 30 tiếng. Bạn bước lên xe ô tô, mở ứng dụng nghe lên và phát hiện tiến trình phát đã bị đặt lại về phút số 0. Nếu đã từng cố nhồi nhét thư viện sách nói vào Plex hoặc Jellyfin, rất có thể bạn đã gặp phải đúng cơn đau đầu này.

Các máy chủ media thông thường xử lý sách nói tương tự như các bản nhạc tiêu chuẩn. Chỉ cần chuyển từ trình duyệt trên máy tính sang điện thoại khi đang đi tàu điện ngầm, bạn sẽ gặp ngay tình trạng mất mốc thời gian phát, thiếu phân chia chương hồi và các file nhiều đĩa bị xáo trộn. Dân homelab thường xử lý nội dung âm thanh dạng lời thoại (spoken-word) theo một trong ba mô hình sau:

  • Tận dụng máy chủ âm nhạc (Navidrome / Jellyfin): Rất tuyệt vời cho các album nhạc định dạng FLAC và MP3. Tuy nhiên, chúng thiếu khả năng phân tích chương hồi gốc của file .m4b, không có tính năng điều chỉnh tốc độ đọc giữ nguyên cao độ (như 1.25x hay 1.75x) và thiếu dấu trang chi tiết. Việc tiếp tục nghe lại một file qua kết nối 4G chập chờn cũng rất thường xuyên gặp lỗi.
  • Hệ thống kết hợp Plex và ứng dụng bên thứ ba (Plex + Prologue/Chronicle): Hoạt động rất mượt mà trên iOS với Prologue. Mặc dù vậy, giải pháp này phụ thuộc vào các công cụ cào dữ liệu (scraper) bên thứ ba kém ổn định cùng các quy tắc đặt tên file nghiêm ngặt. Người dùng Android thì hoàn toàn bị bỏ rơi khi không có ứng dụng chuyên dụng tương đương.
  • Máy chủ sách nói chuyên dụng (Audiobookshelf): Được xây dựng chuyên biệt ngay từ đầu cho sách nói và podcast. Hệ thống kết hợp cơ chế cache SQLite cục bộ với khả năng đồng bộ theo thời gian thực qua WebSocket. Nó cũng tự động thu thập metadata trực tiếp từ Audible, Google Books và OpenLibrary, đồng thời có các ứng dụng di động chỉn chu cho cả Android lẫn iOS.

Ưu và nhược điểm khi chạy Audiobookshelf trên Docker

Audiobookshelf vận hành rất tiết kiệm tài nguyên—khi ở trạng thái rảnh rỗi chỉ tiêu tốn khoảng 120MB đến 200MB RAM. Dù vậy, nó vẫn có một số đặc thù vận hành mà bạn nên nắm rõ trước khi pull image về.

Ưu điểm

  • Đồng bộ tiến trình chuẩn xác từng giây: Kết nối WebSocket liên tục giúp theo dõi tiến trình phát chính xác đến từng giây trên cả trình phát web, Android và iOS.
  • Tự động trích xuất chương hồi: Trình phân tích tự động đọc các điểm cue nhúng sẵn và tiêu đề chương trực tiếp từ các file .m4b, .mp3 và .flac mà không cần đến file sidecar phụ trợ bên ngoài.
  • Lưu trữ metadata tách biệt: Tiểu sử tác giả, thứ tự tập trong bộ và ảnh bìa độ phân giải cao được lưu trữ trong cache cục bộ. Toàn bộ thư viện file âm thanh gốc của bạn được giữ nguyên vẹn hoàn toàn.
  • Trình tải podcast tích hợp sẵn: Trình phân tích cú pháp RSS tích hợp sẵn sẽ tự động tải về các tập mới theo lịch trình, giúp bạn không cần đến các công cụ riêng biệt như TubeArchivist hay Podurama.

Nhược điểm

  • Yêu cầu nghiêm ngặt về phân quyền: Tiến trình bên trong container bắt buộc User ID (PUID) và Group ID (PGID) phải khớp chính xác. Nếu quyền hạn trên máy host bị lệch, SQLite sẽ bị khóa kèm lỗi SQLITE_BUSY hoặc âm thầm thất bại trong các đợt quét thư viện định kỳ.
  • Yêu cầu hỗ trợ WebSocket trên Proxy: Do quá trình đồng bộ theo thời gian thực phụ thuộc vào kết nối song công liên tục, các reverse proxy như Nginx, Traefik hoặc Cloudflare bắt buộc phải chuyển tiếp các upgrade header một cách rõ ràng. Chỉ cần thiếu một header, phiên kết nối trên thiết bị di động sẽ rơi vào vòng lặp polling bị lỗi.

Kiến trúc thiết lập khuyến nghị

Tuyệt đối không đặt các file cơ sở dữ liệu trực tiếp trên ổ chia sẻ mạng (network share). Để tránh tình trạng khóa file và thời gian quét thư viện chậm chạp, hãy tách riêng phần cấu hình khỏi các file media của bạn.

Hãy lưu cấu hình và cơ sở dữ liệu SQLite trên ổ SSD cục bộ tốc độ cao (chẳng hạn như /opt/audiobookshelf). Còn kho file âm thanh dung lượng lớn, hãy lưu trên mảng ổ cứng cơ HDD hoặc ZFS pool, được mount cục bộ hoặc thông qua NFS.

/opt/audiobookshelf/             # Ổ SSD NVMe/SATA cục bộ
├── config/                      # Cài đặt ứng dụng, người dùng và session token
└── metadata/                    # Cơ sở dữ liệu SQLite, ảnh bìa đã cache, cache stream

/mnt/storage/media/              # Bộ lưu trữ dung lượng lớn (ZFS, mảng Unraid hoặc mergerfs)
├── audiobooks/                  # Tác giả/Bộ truyện/Tên sách/Sách.m4b
└── podcasts/                    # Tên chương trình/Tập - Tiêu đề.mp3

Bố cục này giúp bạn tránh được rất nhiều rắc rối. Kể cả khi các ổ cứng cơ rơi vào trạng thái ngủ sau 20 phút không hoạt động hoặc ZFS pool đang chạy tác vụ scrub nặng hàng tháng, giao diện web vẫn duy trì tốc độ phản hồi nhanh chóng. Cơ sở dữ liệu SQLite vẫn mượt mà vì mọi truy vấn I/O đều được thực thi trực tiếp trên bộ nhớ flash cục bộ.

Hướng dẫn triển khai chi tiết

Dưới đây là các bước để thiết lập cấu trúc thư mục trên host, viết cấu hình Compose, thiết lập định tuyến proxy cho WebSocket và kiểm tra tính năng đồng bộ phát lại giữa các thiết bị.

Bước 1: Chuẩn bị thư mục trên host và phân quyền UID/GID

Chạy các lệnh sau trên Docker host của bạn để tạo các đường dẫn cần thiết và thiết lập quyền sở hữu. Việc chạy dưới quyền người dùng không phải root hoặc khớp với user không có đặc quyền trên hệ thống sẽ tránh được hiện tượng xung đột phân quyền trên các mount phân vùng lưu trữ bền vững (persistent mounts).

# Kiểm tra ID người dùng và ID nhóm hiện tại của bạn
id
# Kết quả mẫu: uid=1000(ubuntu) gid=1000(ubuntu)

# Tạo các thư mục runtime, cấu hình và media
sudo mkdir -p /opt/audiobookshelf/config /opt/audiobookshelf/metadata
sudo mkdir -p /mnt/storage/media/audiobooks /mnt/storage/media/podcasts

# Phân quyền sở hữu cho người dùng không đặc quyền của bạn (UID/GID 1000)
sudo chown -R 1000:1000 /opt/audiobookshelf
sudo chmod -R 755 /opt/audiobookshelf

Bước 2: Định nghĩa file cấu hình Docker Compose

Tạo một thư mục riêng cho stack của bạn và mở một file compose mới:

mkdir -p ~/stacks/audiobookshelf
cd ~/stacks/audiobookshelf
nano docker-compose.yml

Dán cấu hình dành cho môi trường production sau đây vào:

services:
  audiobookshelf:
    image: ghcr.io/advplyr/audiobookshelf:latest
    container_name: audiobookshelf
    restart: unless-stopped
    environment:
      - AUDIOBOOKSHELF_UID=1000
      - AUDIOBOOKSHELF_GID=1000
      - TZ=Etc/UTC
      - PORT=13378
    ports:
      - "13378:13378"
    volumes:
      - /opt/audiobookshelf/config:/config
      - /opt/audiobookshelf/metadata:/metadata
      - /mnt/storage/media/audiobooks:/audiobooks:ro
      - /mnt/storage/media/podcasts:/podcasts
    healthcheck:
      test: ["CMD", "nc", "-z", "127.0.0.1", "13378"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s

Mẹo bảo mật: Mount /mnt/storage/media/audiobooks với cờ chỉ đọc :ro sẽ ngăn container vô tình ghi đè hoặc xóa các file gốc. Chỉ bỏ cờ :ro nếu bạn muốn Audiobookshelf trực tiếp chỉnh sửa các thẻ ID3 nhúng sẵn trong file.

Bước 3: Triển khai và kiểm tra log khi khởi chạy

Khởi động container ở chế độ chạy ngầm (detached mode) và kiểm tra tiến trình khởi động ban đầu:

docker compose up -d
docker compose logs -f audiobookshelf

Xác nhận rằng cơ sở dữ liệu đã được khởi tạo và trình lắng nghe socket đã liên kết thành công:

[INFO] Khởi tạo db: Đã kết nối SQLite
[INFO] Server đang lắng nghe trên cổng 13378
[INFO] Đã khởi tạo máy chủ Websocket

Nếu bạn thấy các cảnh báo EACCES hoặc SQLITE_BUSY, hãy kiểm tra lại xem UID 1000 đã thực sự sở hữu /opt/audiobookshelf/config và /opt/audiobookshelf/metadata chưa bằng lệnh ls -la /opt/audiobookshelf.

Bước 4: Cấu hình Reverse Proxy với các Header WebSocket

Việc đưa Audiobookshelf ra ngoài Internet qua HTTPS thông qua Nginx đòi hỏi phải xử lý riêng cho WebSocket. Nếu thiếu các header Upgrade, các ứng dụng client trên thiết bị di động sẽ bị rớt kết nối và không thể đẩy mốc thời gian đang nghe lên máy chủ.

server {
    listen 443 ssl http2;
    server_name audio.homelab.local;

    # Đường dẫn chứng chỉ SSL
    ssl_certificate /etc/letsencrypt/live/audio.homelab.local/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/audio.homelab.local/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:13378;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Các header nâng cấp WebSocket quan trọng
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

Bước 5: Thiết lập thư viện và kiểm tra tính năng đồng bộ client

  1. Mở trình duyệt và truy cập vào http://<HOST-IP>:13378.
  2. Tạo tài khoản quản trị viên root chính của bạn và thiết lập một mật khẩu mạnh.
  3. Chọn Add Library, chọn mục Audiobooks và trỏ đường dẫn tới /audiobooks. Chọn cấu trúc thư mục dạng Author/Title để tăng tốc độ phân tích cú pháp.
  4. Tải ứng dụng client Audiobookshelf từ Google Play, F-Droid hoặc Apple TestFlight / App Store.
  5. Đăng nhập vào máy chủ của bạn trên thiết bị di động, bắt đầu phát một cuốn sách và tua đến mốc thời gian 00:18:45.
  6. Mở đúng cuốn sách đó trên trình duyệt máy tính. Trong vòng 1 đến 2 giây, trình phát trên web sẽ tự động cập nhật khớp với vị trí đang phát trên thiết bị di động.

Bước 6: Sao lưu tự động

Hãy bảo vệ các số liệu thống kê lượt nghe và dấu trang của bạn. Truy cập vào Settings > Backups và bật tính năng tự động sao lưu hàng ngày vào lúc 03:00 AM. Audiobookshelf sẽ đóng gói toàn bộ trạng thái SQLite, phiên người dùng và dấu trang tùy chỉnh vào các tệp nén .tar nhỏ gọn bên trong /opt/audiobookshelf/metadata/backups. Hãy đồng bộ thư mục này sang một nơi lưu trữ từ xa (off-site) bằng Restic hoặc Rclone để đảm bảo khả năng khôi phục toàn diện khi có sự cố.

Share: