Compose stack (MariaDB + Koha built from the Koha project's Debian packages), headless bootstrap replacing the web installer, seeded item types and LoC Z39.50 server, and an install.sh that installs Docker, generates passwords and waits for the site. End-to-end testing in progress. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
127 lines
4.8 KiB
Bash
Executable File
127 lines
4.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" "$@"; }
|
|
|
|
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 " 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
|
|
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}")
|
|
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"
|
|
fi
|
|
|
|
if [ -n "${EXTRA_CA:-}" ]; then
|
|
cp "$EXTRA_CA" koha/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)..."
|
|
dc build
|
|
c_info "Starting..."
|
|
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
|
|
if ! docker ps --format '{{.Names}}' | grep -qx library-koha; then c_err "library-koha stopped - 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
|