Files
ubuntu-based-kiosk/library/install.sh
T
Claude 2ef8f68b9b library: add Quick Add page (scan an ISBN, the book is added)
A small standard-library Python service next to Koha:
- looks the code up via Z39.50 (yaz) then Open Library over HTTPS
- checks Koha for an existing copy (ISBN-10/13 and UPC/EAN aware)
- adds the record + an item with the next barcode via Koha's REST API
- scanner-friendly UI: auto-focus, audible feedback, duplicate card with
  'add another copy', manual entry when nothing is found, undo, and the
  barcode range to print labels for
Also: enable RESTBasicAuth, map 024$a to the ean column, wire the service,
port and credentials into compose/install.sh.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 10:24:54 +00:00

148 lines
5.8 KiB
Bash
Executable File

#!/bin/bash
# One-command setup for a self-hosted home library (Koha) on Ubuntu/Debian.
#
# sudo ./install.sh # install Docker if needed, build, start
# sudo ./install.sh --status # show URLs and container health
# sudo ./install.sh --update # rebuild with the latest Koha and restart
# sudo ./install.sh --stop | --start
#
# Optional environment variables:
# LIBRARY_NAME="Smith Family Library" name shown on the catalogue
# OPAC_PORT=8080 STAFF_PORT=8081 preferred host ports (next free is used)
# EXTRA_CA=/path/to/proxy-ca.crt trust an extra CA while building
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$HERE"
c_info() { printf '\033[0;34m[INFO]\033[0m %s\n' "$*"; }
c_ok() { printf '\033[0;32m[ OK ]\033[0m %s\n' "$*"; }
c_warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*"; }
c_err() { printf '\033[0;31m[FAIL]\033[0m %s\n' "$*" >&2; }
[ "$(id -u)" = 0 ] || { c_err "Run with sudo: sudo $0 $*"; exit 1; }
RUN_USER="${SUDO_USER:-root}"
# --- Docker -------------------------------------------------------------------
ensure_docker() {
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
c_ok "Docker $(docker --version | awk '{print $3}' | tr -d ,) with Compose is installed"
else
c_info "Installing Docker (official get.docker.com script)..."
command -v curl >/dev/null 2>&1 || { apt-get update -qq && apt-get install -y -qq curl ca-certificates; }
curl -fsSL https://get.docker.com | sh
docker compose version >/dev/null 2>&1 || apt-get install -y -qq docker-compose-plugin
c_ok "Docker installed"
fi
systemctl enable --now docker >/dev/null 2>&1 || true
if [ "$RUN_USER" != root ] && ! id -nG "$RUN_USER" | tr ' ' '\n' | grep -qx docker; then
usermod -aG docker "$RUN_USER" && c_info "Added $RUN_USER to the docker group (log out/in to use docker without sudo)"
fi
docker info >/dev/null 2>&1 || { c_err "The Docker daemon is not running."; exit 1; }
}
port_in_use() { ss -tlnH "sport = :$1" 2>/dev/null | grep -q .; }
free_port() { local p="$1"; while port_in_use "$p"; do p=$((p + 1)); done; echo "$p"; }
rand() { head -c 64 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c "$1"; }
lan_ip() { hostname -I 2>/dev/null | awk '{print $1}'; }
dc() { docker compose --project-directory "$HERE" "$@"; }
# Docker Hub rate-limits anonymous pulls (HTTP 429), which is common on shared
# IPs; retry with a growing pause instead of failing the whole install.
with_retry() {
local n=1 max=5 wait=15
until "$@"; do
[ "$n" -ge "$max" ] && { c_err "Failed after $max attempts: $*"; return 1; }
c_warn "Attempt $n failed (Docker Hub rate limit or network?) - retrying in ${wait}s..."
sleep "$wait"; n=$((n + 1)); wait=$((wait * 2))
done
}
show_status() {
[ -f .env ] || { c_err "Not installed yet - run: sudo ./install.sh"; exit 1; }
# shellcheck disable=SC1091
. ./.env
local ip; ip="$(lan_ip)"; ip="${ip:-localhost}"
dc ps
echo
echo " Catalogue (public): http://$ip:$OPAC_PORT"
echo " Staff interface: http://$ip:$STAFF_PORT"
echo " Add books (scan): http://$ip:${QUICKADD_PORT:-8082} (same login)"
echo " Staff login: $ADMIN_USER / $ADMIN_PASSWORD (also stored in $HERE/.env)"
echo
}
case "${1:-}" in
--status) show_status; exit 0 ;;
--stop) dc stop; exit 0 ;;
--start) dc start; exit 0 ;;
--update)
ensure_docker
with_retry dc build --pull --no-cache koha
dc up -d
show_status; exit 0 ;;
"" ) ;;
*) c_err "Unknown option: $1"; exit 1 ;;
esac
ensure_docker
# --- Settings (.env is created once and never overwritten) ------------------------
if [ ! -f .env ]; then
c_info "Generating settings and passwords..."
TZ_NOW="$(cat /etc/timezone 2>/dev/null || timedatectl show -p Timezone --value 2>/dev/null || echo UTC)"
umask 077
cat > .env << ENVEOF
# Generated by install.sh - keep this file private.
LIBRARY_NAME="${LIBRARY_NAME:-My Library}"
TZ=${TZ_NOW}
OPAC_PORT=$(free_port "${OPAC_PORT:-8080}")
STAFF_PORT=$(free_port "${STAFF_PORT:-8081}")
QUICKADD_PORT=$(free_port "${QUICKADD_PORT:-8082}")
ADMIN_USER=admin
ADMIN_PASSWORD=$(rand 16)
DB_ROOT_PASSWORD=$(rand 32)
DB_PASSWORD=$(rand 32)
KOHA_SUITE=stable
ENVEOF
umask 022
[ "$RUN_USER" = root ] || chown "$RUN_USER" .env
else
c_info "Re-using existing .env"
# Settings added in later versions: append them to an older .env.
if ! grep -q '^QUICKADD_PORT=' .env; then
echo "QUICKADD_PORT=$(free_port 8082)" >> .env
fi
fi
if [ -n "${EXTRA_CA:-}" ]; then
cp "$EXTRA_CA" koha/certs/extra-ca.crt
cp "$EXTRA_CA" quickadd/certs/extra-ca.crt
c_info "Trusting extra CA during the build: $EXTRA_CA"
fi
# --- Build and start ---------------------------------------------------------------
c_info "Building the Koha image (first time: 5-10 minutes, downloads ~1 GB)..."
with_retry dc build
c_info "Starting..."
with_retry dc up -d
# --- Wait until the site answers ---------------------------------------------------
# shellcheck disable=SC1091
. ./.env
c_info "First start installs the database - waiting for the staff interface..."
for i in $(seq 1 120); do
if curl -fsS -o /dev/null "http://127.0.0.1:$STAFF_PORT/" 2>/dev/null; then READY=1; break; fi
# One restart is tolerated (Docker's restart policy); a crash loop is not.
if [ "$(docker inspect -f '{{.RestartCount}}' library-koha 2>/dev/null || echo 99)" -ge 3 ]; then
c_err "library-koha keeps crashing - see: docker logs library-koha"; exit 1
fi
sleep 5
done
[ "${READY:-0}" = 1 ] || { c_err "Timed out - see: docker logs -f library-koha"; exit 1; }
c_ok "Your library is running."
echo
show_status