Add Library Catalog addon, lib/docker.sh and tests

- lib/docker.sh: shared Docker/Compose tooling (on-demand install via apt or
  get.docker.com, compose wrapper, container state/health, HTTP wait)
- menus/addon_library.sh: install/update/uninstall the Koha + Quick Add stack
  from library/ to /opt/kiosk-library, add the catalogue as a kiosk page
  (duration 0), choose kiosk-only vs LAN access, show addresses and login
- wire into install.sh menu, complete_uninstall and clone_settings
- tests/addon_library.test.sh: 41 checks against a stub sudo/docker and a
  fixture stack (no root, Docker or network needed)

Note: the 'network access' choice relies on a BIND_ADDR setting in the
library stack, added in a follow-up commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
This commit is contained in:
Claude
2026-11-06 10:29:22 +00:00
parent 2ef8f68b9b
commit 24c95c91f2
7 changed files with 655 additions and 4 deletions
+106
View File
@@ -0,0 +1,106 @@
#!/bin/bash
################################################################################
# lib/docker.sh - Docker / Compose tooling shared by every addon that runs a
# containerised service (the Library Catalog addon is the first).
#
# Everything here goes through `sudo docker ...`: the kiosk admin runs
# install.sh as a regular user and is deliberately NOT put in the `docker`
# group (docker-group membership is root-equivalent).
#
# Depends on: lib/menu.sh (log_* helpers) being sourced first.
################################################################################
# Overridable so tests can substitute stubs for the real binaries.
: "${DOCKER_BIN:=docker}"
docker_is_installed() {
command -v "$DOCKER_BIN" >/dev/null 2>&1 \
&& sudo "$DOCKER_BIN" compose version >/dev/null 2>&1
}
docker_daemon_running() {
sudo "$DOCKER_BIN" info >/dev/null 2>&1
}
# Install Docker Engine + the Compose plugin if they are missing.
#
# Ubuntu 24.04+ ships both in its own archive (docker.io, docker-compose-v2):
# preferred because it also works through the offline apt bundle's mirror and
# gets security updates with everything else. On releases without
# docker-compose-v2 (22.04), fall back to Docker's official install script.
docker_ensure_installed() {
if docker_is_installed; then
docker_daemon_running || sudo systemctl enable --now docker >/dev/null 2>&1 || true
log_success "Docker $(sudo "$DOCKER_BIN" --version 2>/dev/null | awk '{print $3}' | tr -d ,) is installed"
return 0
fi
log_info "Installing Docker..."
if ! sudo apt-get update -qq; then
log_error "apt update failed - check the network/package sources and try again"
return 1
fi
if apt-cache show docker-compose-v2 >/dev/null 2>&1; then
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io docker-compose-v2 \
|| { log_error "Installing docker.io / docker-compose-v2 failed"; return 1; }
else
log_info "This Ubuntu release has no docker-compose-v2 package - using Docker's install script"
have_internet 2>/dev/null || { log_error "No internet connection - can't download Docker"; return 1; }
local script
script=$(mktemp)
if ! curl -fsSL https://get.docker.com -o "$script"; then
rm -f "$script"
log_error "Could not download Docker's install script"
return 1
fi
sudo sh "$script" || { rm -f "$script"; log_error "Docker's install script failed"; return 1; }
rm -f "$script"
fi
sudo systemctl enable --now docker >/dev/null 2>&1 || true
if ! docker_is_installed || ! docker_daemon_running; then
log_error "Docker was installed but is not working - check: sudo systemctl status docker"
return 1
fi
log_success "Docker installed"
}
# docker_compose DIR args... -> sudo docker compose --project-directory DIR args...
docker_compose() {
local dir="$1"; shift
sudo "$DOCKER_BIN" compose --project-directory "$dir" "$@"
}
# docker_container_state NAME -> running | exited | restarting | ... | missing
docker_container_state() {
sudo "$DOCKER_BIN" inspect -f '{{.State.Status}}' "$1" 2>/dev/null || echo "missing"
}
# docker_container_health NAME -> healthy | unhealthy | starting | none | missing
docker_container_health() {
sudo "$DOCKER_BIN" inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$1" 2>/dev/null \
|| echo "missing"
}
# docker_wait_http URL [TIMEOUT_SECONDS]
# Polls until URL answers 2xx/3xx; returns 1 on timeout.
docker_wait_http() {
local url="$1" timeout="${2:-300}" waited=0
while (( waited < timeout )); do
if curl -fsS -o /dev/null -m 5 "$url" 2>/dev/null; then
return 0
fi
sleep 5
waited=$((waited + 5))
done
return 1
}
# docker_free_port START -> prints the first TCP port >= START nothing listens on.
docker_free_port() {
local port="$1"
while ss -tlnH "sport = :${port}" 2>/dev/null | grep -q .; do
port=$((port + 1))
done
echo "$port"
}