README.pdf
#!/bin/bash
#  ____  _ _     _ _       _            _
# |  _ \(_) |   | (_)     | |          | |
# | |_) |_| |__ | |_  ___ | |_ ___  ___| |__   __ _
# |  _ <| | '_ \| | |/ _ \| __/ _ \/ __| '_ \ / _` |
# | |_) | | |_) | | | (_) | ||  __/ (__| | | | (_| |
# |____/|_|_.__/|_|_|\___/ \__\___|\___|_| |_|\__,_|
#
#       Digital books need libraries too
#
# This install script is intended for use with Debian based
# distributions. More specifically, Debian Buster, which is 
# currently the latest distribution release from the Debian 
# project.
#
# License
# =======
#
# Copyright (C) 2019 Bibliotecha Contributors 
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see http://www.gnu.org/licenses/.
#
# Thanks
# =====
#
# This script is based on the good work of the Freedombone project:
# https://code.freedombone.net/bashrc/freedombone/src/stretch/src/freedombone

APT_CMD="apt -q"
APT_INSTALL_CMD="$APT_CMD install -y --no-install-recommends"

CALIBRE_DATABASE_PATH="/opt/calibre-database"
CAPTIVE_PORTAL_PATH="/var/www/bibliotecha"
CALIBRE_WEB_PATH="/var/www/calibre-web"
LIGHTTPD_CONFIG_PATH="/etc/lighttpd/bibliotecha"

function ensure_root_account {
  echo "Checking user account ..."

  if ! id | grep -q root; then
    echo ""
    echo "This script must be run as root"
    echo "You can switch to the root user account with:"
    echo "$ sudo -i"
    echo ""
    exit 0
  fi
}

function show_bibliotecha_banner {
  echo ""
  echo "   ____  _ _     _ _       _            _ "
  echo "  |  _ \(_) |   | (_)     | |          | |"
  echo "  | |_) |_| |__ | |_  ___ | |_ ___  ___| |__   __ _ " 
  echo "  |  _ <| | '_ \| | |/ _ \| __/ _ \/ __| '_ \ / _' |"
  echo "  | |_) | | |_) | | | (_) | ||  __/ (__| | | | (_| |"
  echo "  |____/|_|_.__/|_|_|\___/ \__\___|\___|_| |_|\__,_|"
  echo ""
  echo "           Digital books need libraries too"
  echo ""
}

function show_introduction_text {
  echo ""
  echo "Welcome to the Bibliotecha automatic installation script"
  echo ""
  echo "This script will attempt to setup the following infrastructure:"
  echo ""
  echo "* A Python 3 environment"
  echo "* A Wifi hotspot with DNS and DHCP services enabled"
  echo "* A web server"
  echo "* A Calibre library"
  echo "* A Calibre-web service"
  echo ""
  echo "Once the script is finished, please see post-install steps"
  echo "that are documented and available from the Bibliotecha manual:"
  echo ""
  echo "      https://manual.bibliotecha.info/#post-installation"
  echo ""
  echo "If anything goes wrong, please see the troubleshooting guide"
  echo ""
  echo "      https://manual.bibliotecha.info/#troubleshooting"
  echo ""
  echo "Waiting a few seconds before moving on ..."
  sleep 10
}

function ensure_buster_based_distribution {
  echo "Checking distribution ..." 

  local installing_on_buster_based=1

  if [ ! -f /etc/apt/sources.list ]; then
    installing_on_buster_based=
  else
    if ! grep -q 'buster' /etc/apt/sources.list; then
        installing_on_buster_based=
    fi
    if ! grep -q 'debian' /etc/apt/sources.list; then
        if ! grep -q 'raspbian' /etc/apt/sources.list; then
            installing_on_buster_based=
        fi
    fi
  fi

  if [ ! $installing_on_buster_based ]; then
    echo "You should only run this on a Debian Buster based system"
    exit 1
  fi
}

function run_apt_update {
  echo "Running a package listing update ..."

  $APT_CMD update
}

function install_networking_packages {
  echo "Installing networking packages ..."

  $APT_INSTALL_CMD \
    dhcpcd \
    dnsmasq \
    dnsutils \
    hostapd \
    wireless-tools
}

function stop_networking_services {
  echo "Stopping the networking services ..."

  local services="dnsmasq hostapd"

  # shellcheck disable=SC2086
  systemctl stop ${services}
}

function disable_avahi_service {
  echo "Disable the avahi service ..."

  systemctl stop avahi-daemon
  systemctl disable avahi-daemon
}

function ensure_predictable_network_interfaces {
  echo "Ensuring predictable networking interfaces ..."

  # shellcheck disable=SC2010
  # shellcheck disable=SC2155
  local ethernet_interface=$(ls /sys/class/net/ | grep en)

  if [[ -z "${ethernet_interface}" ]]; then
    echo ""
    echo "Could not determine the ethernet interface"
    echo "Please ensure you've configure 'predictable network interfaces'"
    echo "Please see https://manual.bibliotecha.info/#pre-installation for more"
    echo ""
    exit 1
  fi

  # shellcheck disable=SC2010
  # shellcheck disable=SC2155
  local wireless_interface=$(ls /sys/class/net/ | grep wl)

  if [[ -z "${wireless_interface}" ]]; then
    echo ""
    echo "Could not determine the wireless interface"
    echo "Please ensure you've configure 'predictable network interfaces'"
    echo "Please see https://manual.bibliotecha.info/#pre-installation for more"
    echo ""
    exit 1
  fi
}

function configure_network_interfaces {
  echo "Configuring networking interfaces ..."

  # shellcheck disable=SC2010
  # shellcheck disable=SC2155
  local ethernet_interface=$(ls /sys/class/net/ | grep en)
  local ethernet_filename="/etc/network/interfaces.d/${ethernet_interface}"

  { echo "auto eth0";
    echo "allow-hotplug ${ethernet_interface}";
    echo "iface ${ethernet_interface} inet dhcp"; } > "${ethernet_filename}"

  # shellcheck disable=SC2010
  # shellcheck disable=SC2155
  local wireless_interface=$(ls /sys/class/net/ | grep wl)
  local wireless_filename="/etc/network/interfaces.d/${wireless_interface}"

  { echo "auto ${wireless_interface}";
    echo "iface ${wireless_interface} inet static";
    echo "  address 10.0.0.1";
    echo "  netmask 255.255.255.0"; } > "${wireless_filename}"
}

function configure_dnsmasq {
  echo "Configuring dnsmasq ..."

  # shellcheck disable=SC2010
  # shellcheck disable=SC2155
  local wireless_interface=$(ls /sys/class/net/ | grep wl)
  local wireless_filename="/etc/dnsmasq.d/${wireless_interface}.conf"

  { echo "bogus-priv"
    echo "server=/library/10.0.0.1"
    echo "local=/library/"
    echo "address=/#/10.0.0.1"
    echo "interface=${wireless_interface}"
    echo "domain=library"
    echo "dhcp-range=10.0.0.50,10.0.0.200,255.255.255.0,12h"
    echo "dhcp-option=3,10.0.0.1"
    echo "dhcp-option=6,10.0.0.1"
    echo "dhcp-authoritative"; } > "${wireless_filename}"
}

function configure_hostapd {
  echo "Configuring hostapd ..."

  # shellcheck disable=SC2010
  # shellcheck disable=SC2155
  local wireless_interface=$(ls /sys/class/net/ | grep wl)
  local hostapd_filename="/etc/hostapd/hostapd.conf"

  { echo "interface=${wireless_interface}"
    echo "ssid=Bibliotecha"
    echo "hw_mode=g"
    echo "channel=11"
    echo "auth_algs=1"; } > "${hostapd_filename}"

  sed -i \
    's/#DAEMON_CONF=""/DAEMON_CONF="\/etc\/hostapd\/hostapd.conf"/g' \
    /etc/default/hostapd
}

function configure_etc_hosts {
  echo "Configuring /etc/hosts entry ..."

  if ! grep -q "bibliotecha.library" /etc/hosts; then
    echo '10.0.0.1        bibliotecha.library' >> /etc/hosts
  fi
}

function enable_networking_services {
  echo "Enabling network services ..."

  local services="dnsmasq hostapd"

  systemctl unmask hostapd

  # shellcheck disable=SC2086
  systemctl enable ${services}
}

function install_webserver {
  echo "Installing lighttpd ..."

  $APT_INSTALL_CMD lighttpd
}

function configure_webserver {
  echo "Configuring bibliotecha under lighttpd ..."

  if ! grep -q mod_proxy /etc/lighttpd/lighttpd.conf; then
    echo ""
    echo 'server.modules += ("mod_proxy",)' >> /etc/lighttpd/lighttpd.conf
  fi

  if ! grep -q bibliotecha /etc/lighttpd/lighttpd.conf; then
    echo ""
    echo 'include "bibliotecha/bibliotecha.conf"' >> /etc/lighttpd/lighttpd.conf
  fi

  sed -i \
    's/server.document-root.*/server.document-root        = "\/var\/www"/g' \
    /etc/lighttpd/lighttpd.conf

  mkdir -p "$LIGHTTPD_CONFIG_PATH"

  { echo 'server.error-handler-404 = "/bibliotecha/index.html"'
    echo ""
    # shellcheck disable=SC2016
    echo '$HTTP["host"] == "bibliotecha.library" {'
    echo '  proxy.server = ("" => (("host" => "127.0.0.1", "port" => "8083")))'
    echo '}'; } > "$LIGHTTPD_CONFIG_PATH/bibliotecha.conf"
}

function configure_captive_portal {
  echo "Configuring the captive portal page ..."

  $APT_INSTALL_CMD wget

  captive_portal_url="https://git.vvvvvvaria.org/varia/bibliotecha-captive-portal/raw/branch/master/index.html"

  mkdir -p "$CAPTIVE_PORTAL_PATH"
  wget "$captive_portal_url" -O "$CAPTIVE_PORTAL_PATH/index.html"
  chown -R www-data: "$CAPTIVE_PORTAL_PATH"
}

function install_calibre {
  echo "Install Calibre ..."

  $APT_INSTALL_CMD calibre
}

function configure_calibre_database {
  echo "Configuring the Calibre database ..."

  mkdir -p "$CALIBRE_DATABASE_PATH"

  # A dirty hack to initialise a new calibre database
  /usr/bin/calibredb restore_database --really-do-it --with-library "$CALIBRE_DATABASE_PATH"
}

function install_calibreweb {
  echo "Installing Calibre-web ..."

  $APT_INSTALL_CMD git python3 python3-pip python3-dev python3-venv

  calibre_web_url="https://github.com/janeczku/calibre-web"

  if [ ! -d "$CALIBRE_WEB_PATH" ]; then
    git clone "$calibre_web_url" "$CALIBRE_WEB_PATH"
  fi

  if [ ! -d "$CALIBRE_WEB_PATH/.venv" ]; then
    # shellcheck disable=SC1091
    cd "$CALIBRE_WEB_PATH" && \
      python3 -m venv .venv && \
      .venv/bin/pip install -r requirements.txt
  fi

  chown -R www-data: "$CALIBRE_WEB_PATH"
}

function configure_calibreweb {
  echo "Configuring Calibre-web ..."

  { echo "Description=Calibre-Web"
    echo ""
    echo "[Service]"
    echo "Type=simple"
    echo "User=root"
    echo "ExecStart=$CALIBRE_WEB_PATH/.venv/bin/python $CALIBRE_WEB_PATH/cps.py"
    echo "WorkingDirectory=$CALIBRE_WEB_PATH"
    echo ""
    echo "[Install]"
    echo "WantedBy=multi-user.target"; } > /etc/systemd/system/cps.service

    systemctl enable cps.service
}

function show_post_install_banner {
  echo ""
  echo "Installation complete!"
  echo ""
}

function install_new_motd {
  echo "Installing the new MOTD ..."

  { echo ""
    echo ""
    echo "   ____  _ _     _ _       _            _ "
    echo "  |  _ \(_) |   | (_)     | |          | |"
    echo "  | |_) |_| |__ | |_  ___ | |_ ___  ___| |__   __ _ " 
    echo "  |  _ <| | '_ \| | |/ _ \| __/ _ \/ __| '_ \ / _' |"
    echo "  | |_) | | |_) | | | (_) | ||  __/ (__| | | | (_| |"
    echo "  |____/|_|_.__/|_|_|\___/ \__\___|\___|_| |_|\__,_|"
    echo ""
    echo "           Digital books need libraries too"
    echo ""
    echo ""; } > /etc/motd
}

function reboot_system {
  echo "Rebooting system ..."

  reboot
}

function run_installation {
  ensure_root_account
  ensure_buster_based_distribution
  ensure_predictable_network_interfaces

  show_bibliotecha_banner
  show_introduction_text

  run_apt_update

  install_networking_packages
  stop_networking_services
  disable_avahi_service
  configure_network_interfaces
  configure_dnsmasq
  configure_hostapd
  configure_etc_hosts
  enable_networking_services

  install_webserver
  configure_webserver
  configure_captive_portal

  install_calibre
  configure_calibre_database
  install_calibreweb
  configure_calibreweb

  show_post_install_banner
  install_new_motd
  reboot_system
}

run_installation
exit 0 
bibliotecha.sh
README.md
../