Electronic Books Library

OPDS Logo

COPS is the Calibre OPDS and HTML server written in PHP. A web-based light alternative to the Calibre built-in content server. It allows you to serve electronic books (epub, mobi, pdf, etc.) from your eBook library website either trough web pages accessible with a webbrowser or directly with a reading device or reading app on a mobile device trough OPDS.

In this setup we assume, you manage your eBook collection with the Electronic Books electronic book library software on your desktop computer and synchronize the library with ownCloud Desktop Client to the Cloud Storage. This way all changes made in Calibre on your desktop are instantly abvailable anywhere else.

Prerequisites

Network Configuration

IP Addresses

Edit the /etc/network/interfaces.

Add additional static IPv4 and IPv6 addresses for the HTML and OPDS servers:

# books.example.net
iface eth0 inet static
    address 192.0.2.33/24
iface eth0 inet6 static
    address 2001:db8::33/64

# opds.example.net
iface eth0 inet static
    address 192.0.2.34/24
iface eth0 inet6 static
    address 2001:db8::34/64

DNS Records

Create TLSA records:

$ tlsa --create --certificate /etc/ssl/certs/example.net.cert.pem books.example.net
_443._tcp.example.net. IN TLSA 3 0 1 f8df4b2e.......................76a2a0e5
$ tlsa --create --certificate /etc/ssl/certs/example.net.cert.pem opds.example.net
_443._tcp.example.net. IN TLSA 3 0 1 f8df4b2e.......................76a2a0e5

Add host names and TLSA records as follows:

Name

Type

Content

Priority

TTL

books

A

198.51.100.240

300

books

AAAA

2001:db8:1::33

_443._tcp.books

TLSA

3 0 1 f8df4b2e…………………..76a2a0e5

opds

A

198.51.100.240

300

opds

AAAA

2001:db8::34

_443._tcp.opds

TLSA

3 0 1 f8df4b2e…………………..76a2a0e5

Firewall Rules

See Router.

Software Installation

$ sudo -s
$ apt install php-sqlite3
$ mkdir -p /var/www/books.example.net
$ cd /var/www/books.example.net
$ git clone git clone --no-checkout https://github.com/seblucas/cops.gi
$ cd cops
$ git tag
1.0.1
1.1.0
1.1.1
$ git checkout 1.1.1
$ cp config_local.php.example config_local.php
$ chown -R www-data:www-data /var/www/urown.net/books/cops
$ exit
$ cd /var/www/urown.net/books/cops
$ sudo -u www-data composer install --no-dev --optimize-autoloader
$ sudo -u www-data cp config_local.php.example config_local.php

COPS Configuration

Edit config_local.php:

<?php
    if (!isset($config))
        $config = array();

    /*
     ***************************************************
     * Please read config_default.php for all possible
     * configuration items
     ***************************************************
     */

    /*
     * The directory containing calibre's metadata.db file, with sub-directories
     * containing all the formats.
     * BEWARE : it has to end with a /
     */
    $config['calibre_directory'] = '/var/lib/nextcloud/data/user/files/Books/';

    /*
     * SPECIFIC TO NGINX
     * The internal directory set in nginx config file
     * Leave empty if you don't know what you're doing
     */
    $config['calibre_internal_directory'] = '/Books/';

    /*
     * Full URL prefix (with trailing /)
     * useful especially for Opensearch where a full URL is often required
     * For example Mantano, Aldiko and Marvin require it.
     */
    $config['cops_full_url'] = 'https://books.example.net/';

    /*
     * Number of recent books to show
     */
    $config['cops_recentbooks_limit'] = '25';

    /*
     * Catalog's title
     */
    $config['cops_title_default'] = "Books";

    /*
     * Wich header to use when downloading books outside the web directory
     * Possible values are :
     *   X-Accel-Redirect   : For Nginx
     *   X-Sendfile         : For Lightttpd or Apache (with mod_xsendfile)
     *   No value (default) : Let PHP handle the download
     */
    $config['cops_x_accel_redirect'] = "X-Accel-Redirect";

    /*
     * Default timezone
     * Check following link for other timezones :
     * http://www.php.net/manual/en/timezones.php
     */
    $config['default_timezone'] = 'Europe/Paris';

    /*
     * use URL rewriting for downloading of ebook in HTML catalog
     * See README for more information
     *  1 : enable
     *  0 : disable
     */
    $config['cops_use_url_rewriting'] = "1";

    /*
     * split authors by first letter
     * 1 : Yes
     * 0 : No
     */
    $config['cops_author_split_first_letter'] = '0';

    /*
     * split titles by first letter
     * 1 : Yes
     * 0 : No
     */
    $config['cops_titles_split_first_letter'] = '0';

    /*
     * Update Epub metadata before download
     * 1 : Yes (enable)
     * 0 : No
     */
    $config['cops_update_epub-metadata'] = '1';

    /*
     * Enable and configure Send To Kindle (or Email) feature.
     *
     * Don't forget to authorize the sender email you configured in your
     * Kindle's  Approved Personal Document E-mail List.
     */
    $config['cops_mail_configuration'] = array(
                "smtp.host"     => "mail.example.net",
                "smtp.username" => "webmaster@example.net",
                "smtp.password" => "********",
                "smtp.secure"   => "1",
                "smtp.port"     => "587",
                "address.from"  => "webmaster@example.net",
                "subject"       => "[eBook] "
            );

    /*
     * Directory to keep resized thumbnails: allow to resize thumbnails only
     * on first access, then use this cache.
     * $config['cops_thumbnail_handling'] must be ""
     * "" : don't cache thumbnail
     * "/tmp/cache/" (example) : will generate thumbnails in /tmp/cache/
     * BEWARE : it has to end with a /
     */
    $config['cops_thumbnail_cache_directory'] = '/tmp/cache/';

    /*
     * Which template is used by default :
     * 'default'
     * 'bootstrap'
     */
    $config['cops_template'] = 'bootstrap';

Nginx Configuration

Virtual Server

#
# COPS - Calibre OPDS and HTML Server
# books.example.net
#

# Secured HTTPS Site
server {

    server_name books.example.net;

    # IPv6 public global address
    listen      [2001:db8::33]:443 ssl http2 deferred;

    # IPv4 private local address
    listen      192.0.2.26:443 ssl http2 deferred;

    # IPv4 private address (Port-forwarded from NAT firewall/router)
    listen      192.0.2.30:443 ssl http2;

    # TLS certificate (chained) and RSA private key
    ssl_certificate         /etc/dehydrated/rsa_certs/books.example.net/fullchain.pem;
    ssl_certificate_key     /etc/dehydrated/rsa_certs/books.example.net/privkey.pem;

    # Enable stapling of online certificate status protocol (OCSP) repsonse
    include                 /etc/nginx/ocsp-stapling.conf;

    # TLS certificate of signing CA (to validate OCSP repsonse when stapling)
    ssl_trusted_certificate /etc/dehydrated/rsa_certs/books.example.net/chain.pem;

    # RSA cert OCSP stapling repsonse file (pre-generated)
    ssl_stapling_file       /etc/dehydrated/rsa_certs/books.example.net/ocsp_response.der;

    # TLS session cache (type:name:size)
    ssl_session_cache       shared:books.example.net:10m;

    # TLS session ticket keys (rotated every 8 hours, for 24h max.)
    ssl_session_ticket_key  tls_session_keys/books.example.net.1.key;
    ssl_session_ticket_key  tls_session_keys/books.example.net.2.key;
    ssl_session_ticket_key  tls_session_keys/books.example.net.3.key;

    # Strict Transport Security (HSTS)
    include     hsts.conf;

    # Common Server Settings
    #include     server-conf.d/*.conf;
    include     server-conf.d/10_server-security.conf;
    include     server-conf.d/20_client-security.conf;
    include     server-conf.d/50_error-pages.conf;
    include     server-conf.d/70_no_transform.conf;
    #include     server-conf.d/80_client-cache-control.conf; # Not compatible!

    # Content Security Policy (CSP)
    include     csp/books.example.net.csp.conf;

    # Private LAN only access allowed
    include     local-access-only.conf;

    # Public Documents Root
    root        /var/www/example.net/books/cops;

    # COPS Web Application Configuration
    include     webapps/cops.conf;

}

# Unsecured HTTP Site - Redirect to HTTPS Site
server {

    server_name books.example.net;

    # IPv6 public global address
    listen      [2001:db8::33]:80 deferred;

    # IPv4 private local address
    listen      192.0.2.33:80 deferred;

    # IPv4 private address (Port-forwarded from NAT firewall/router)
    listen      192.0.2.30:80;

    # Private LAN only access allowed
    include     local-access-only.conf;

    # Redirect to HTTPS on proper hostname
    return      301 https://$server_name$request_uri;

}

COPS Web Application

#
# COPS
# https://github.com/seblucas/cops
# https://github.com/seblucas/cops/wiki/Full-example-with-Nginx
#

# PHP Server Configuration
include     /etc/nginx/php-handler.conf;

# Use HTML as as directory index
index       feed.php;

location    /Books/ {
    alias   /var/lib/nextcloud/data/user/files/Books/;
    internal;
}

# Rewriting download URLs
# https://github.com/seblucas/cops/wiki/Url-Rewriting-with-COPS
location /download/ {
    rewrite ^/download/(\d+)/(\d+)/.*\.(.*)$ /fetch.php?data=$1&db=$2&type=$3 last;
    rewrite ^/download/(\d+)/.*\.(.*)$ /fetch.php?data=$1&type=$2 last;
    break;
}

location ~ ^/images.*\.(gif|png|ico|jpg)$ {
  gzip off;
  expires 1M;
  add_header Cache-Control "public";
}

location ~ .(js|css|eot|svg|woff|woff2|ttf)$ {
  gzip off;
  expires 1M;
  add_header Cache-Control "public";
}

Content Security Policy

CSP-Builder JSON file /etc/nginx/csp/books.example.net.csp.json:

{
    "base-uri": {
        "self": true
    },
    "default-src": {
        "self": true
    },
    "img-src": {
        "self": true,
        "data": true
    },
    "script-src": {
        "self": true,
        "unsafe-inline": true,
        "unsafe-eval": true
    },
    "style-src": {
        "self": true,
        "unsafe-inline": true
    },
    "form-action": {
        "self": true
    },
    "frame-ancestors": {
      "self": true
    },
    "plugin-types": [],
    "block-all-mixed-content": true,
    "upgrade-insecure-requests": false
}

Generated Nginx configuration /etc/nginx/csp/books.example.net.csp,conf:

add_header Content-Security-Policy
               "base-uri 'self';
                default-src 'self';
                form-action 'self';
                frame-ancestors 'self';
                img-src 'self' data:;
                script-src 'self' 'unsafe-inline' 'unsafe-eval';
                style-src 'self' 'unsafe-inline';
                block-all-mixed-content";

Backup Considerations

We should be covered by our previous configuration and no additional steps for backup are needed.