Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ef968fd10 | ||
|
|
9fb512cdab | ||
|
|
f0a9728016 | ||
|
|
6fdc5f4dd1 | ||
|
|
0af833a39d | ||
|
|
442b45c2e0 | ||
|
|
6f877d1107 | ||
|
|
3d02d28a8c | ||
|
|
7f230c8e73 | ||
|
|
b784d98c9d | ||
|
|
d01919e2ac | ||
|
|
10e991e158 |
@@ -3,3 +3,6 @@
|
||||
# Extra CA certificates dropped in for building behind a proxy
|
||||
koha/certs/*.crt
|
||||
quickadd/certs/*.crt
|
||||
# Generated by install.sh --domain
|
||||
Caddyfile
|
||||
caddy-root.crt
|
||||
|
||||
+116
-5
@@ -1,4 +1,4 @@
|
||||
# Home library: Koha + Quick Add (Docker)
|
||||
# Room for Books: a home library (Koha + the Room for Books site, in Docker)
|
||||
|
||||
A self-hosted library catalogue you can set up in one command:
|
||||
|
||||
@@ -12,9 +12,9 @@ it never overwrites your `.env` (passwords) or your data.
|
||||
|
||||
| What | Address | Login |
|
||||
|---|---|---|
|
||||
| **Your library**: add books, find a book, people, lending, labels, settings | `http://<this machine>:8081` | `admin` and the generated password |
|
||||
| **Add books** (scan an ISBN, or use the phone camera) | `http://<this machine>:8081/add/` | same login (asked once more) |
|
||||
| **Public catalogue** (search only, for a kiosk or visitors) | `http://<this machine>:8080` | none |
|
||||
| **Your library**: add books, find a book, people, lending, labels, settings | `http://<this machine>:8098` | `admin` and the generated password |
|
||||
| **Add books** (scan an ISBN, or use the phone camera) | `http://<this machine>:8098/add/` | same login (asked once more) |
|
||||
| **Public catalogue** (search only, for a kiosk or visitors) | `http://<this machine>:8097` | none |
|
||||
|
||||
For home use you only need the first address: sign in to **Your library**, and use the **Add books**
|
||||
button on its home page. The Add books page has a **Find a book** tab (title, author or ISBN, with the
|
||||
@@ -24,7 +24,7 @@ staff sites the same URL shapes, so they cannot share one address; Add books can
|
||||
|
||||
Ports move up to the next free number if one is taken; the final addresses are printed
|
||||
at the end and by `sudo ./install.sh --status`. Add books is also reachable directly on its own port
|
||||
(`8082`), which is handy behind a proxy but not needed otherwise.
|
||||
(`8099`), which is handy behind a proxy but not needed otherwise.
|
||||
|
||||
## What it is made of
|
||||
|
||||
@@ -203,3 +203,114 @@ koha/ Dockerfile (Koha from the project's Debian repo), entrypoi
|
||||
servers, label templates/layouts)
|
||||
quickadd/ the scan-to-add service (Python standard library + yaz)
|
||||
```
|
||||
|
||||
## Signing in once
|
||||
|
||||
Staff who are signed in to the library site (and allowed to edit the catalogue) open **Add books** without a second login. Koha itself confirms the sign-in; the shared secret is `SSO_SECRET` in `.env`. The read-only `guest` login still uses the Add books prompt, and the direct Add books port never accepts a Koha sign-in. If `SSO_SECRET` is empty, Add books just asks for its own login as before.
|
||||
|
||||
## The home page (the Room for Books site at `/add/`)
|
||||
|
||||
**One address.** `http://<ip>:<port>/` (and later `https://your-domain/` behind Caddy) opens this page; there is no separate
|
||||
port to remember. Koha's own staff screens live on the same address at `/cgi-bin/koha/mainpage.pl`, reached from the menu
|
||||
(*Koha (full staff screens)*). Everyone signs in at the same prompt: staff with the admin login, borrowers with their card number
|
||||
and password, the guest with the guest login.
|
||||
|
||||
One simple page: a **Look up** box (books by title, author or ISBN, and people by name or card number), four big buttons
|
||||
(**Check out**, **Return**, **Add books**, **Add a person**) and **Recently added** books with their covers. The **menu**
|
||||
(top right) holds the rest: browse all books, lend / return, people and library cards, barcodes and settings, export / import,
|
||||
and a link to Koha's own staff screens. Borrowers and the read-only guest see just the lookup, recent books and (borrowers) My books.
|
||||
|
||||
**Adding by title:** on Add books, scan or type an ISBN to add it straight away; type a title or author instead and you get a list
|
||||
of matches (cover, author, year, publisher) from Open Library and the Library of Congress to choose from. Books you already have
|
||||
are marked, with an *Add another copy* button.
|
||||
|
||||
## Everyday use (details)
|
||||
|
||||
One page for the day-to-day, so you rarely need Koha's own screens:
|
||||
|
||||
- **Library**: a cover grid of everything you own. Search by title, author or ISBN; filter by shelf, format
|
||||
(book/DVD/CD) and status (on the shelf / checked out); sort by title, author or newest. Click a book for its
|
||||
page: copies with barcode, status, who has it and when it is due; change a copy's shelf or call number,
|
||||
add another copy, remove a copy, or delete the book. (Title/author edits are one click away in Koha.)
|
||||
- **Lend / return**: scan or type a library card number (or search by name), then scan books to check them out;
|
||||
"Return a book" takes a scan with no person needed; **Renew** extends a loan; the list below shows everything
|
||||
that is out, overdue ones flagged. Check-in goes through Koha's own check-in code, so holds and rules still apply.
|
||||
- **People**: add someone with just a name (the card number is assigned automatically), search, and
|
||||
**Print card** for a library card with a scannable Code 39 barcode.
|
||||
- **Add books**: scan an ISBN (or use the phone camera), as before.
|
||||
|
||||
The read-only `guest` login only sees the Library and book pages (no copy status names, no editing, no lending).
|
||||
|
||||
### Collections, QR cards, more about a book
|
||||
|
||||
- **Collections** (Kids, Cookbooks, ...) are per copy, shown on the book page next to the shelf, and filterable on the
|
||||
Library page. Choose **+ New collection...** (or **+ New shelf...**) in the dropdown to add one: no Koha screens needed.
|
||||
- **Library cards** carry a Code 39 barcode and a **QR code** of the card number. **Save as picture** makes an image to text
|
||||
or AirDrop to a phone; on Lend / return, **Scan card or phone QR with camera** reads either one from a photo.
|
||||
**Print everyone's cards** (People) lays out a sheet of cards.
|
||||
- **More about a book**: a description and subjects from Open Library, plus look-up links (Goodreads, LibraryThing,
|
||||
WorldCat). **AR BookFind** is a link only: Renaissance's terms forbid automated access, so nothing is fetched from it.
|
||||
|
||||
### Reading levels (AR) and spreadsheets
|
||||
|
||||
AR BookFind has no public feed or import, and its terms forbid automated look-ups, so this site does not fetch from it.
|
||||
Instead: the book page has an **Open AR BookFind** link (plus **Copy title** to paste into its search) and boxes to record the
|
||||
**level, points and quiz number** you find there (the read-only guest sees them). **Library page -> Export / import** downloads
|
||||
`library.csv` (one row per copy, with any reading level) and imports a CSV of `isbn, level, points[, quiz]` you made yourself
|
||||
(for example from a spreadsheet or a Renaissance report you are entitled to export). Levels are stored in `reading-levels.json`
|
||||
in the Add books data volume, keyed by ISBN, so they survive a Koha reinstall and are included in the usual backups of that folder.
|
||||
|
||||
### Live camera scanning (needs HTTPS)
|
||||
|
||||
Browsers only allow a live camera on HTTPS pages (or on the machine itself). Put the library site behind Caddy with a real
|
||||
certificate and **Live scan** buttons appear: Add books (scan a stack of ISBNs one after another), Lend / return (live card or
|
||||
phone-QR scan, live book check-out for the chosen person, live returns). Chrome on Android reads codes in the browser; other
|
||||
browsers send small camera frames to the server's decoder a couple of times a second. On plain HTTP the photo buttons stay.
|
||||
|
||||
### Copying reading levels from AR BookFind
|
||||
|
||||
On the AR BookFind book page, select everything (Ctrl/Cmd+A), copy, and paste into the box on the book page here: the level,
|
||||
points, quiz number and interest level fill in by themselves; check them and press Save.
|
||||
|
||||
### Tags, and borrowers signing in
|
||||
|
||||
- **Tags**: any number per book (to read, kids, signed...). Add and remove them on the book page; click one to see every
|
||||
book that has it; the Library page can filter by tag. They are stored in `tags.json` in the Add books data volume, which
|
||||
the usual backups of that folder include.
|
||||
- **Borrowers sign in**: every person gets a login, **their library card number + a password**. Adding someone shows
|
||||
their password once (a different easy one like `maple482`, or the same one for everyone: People -> *Sign-in passwords for new
|
||||
people*). *Give everyone a sign-in* covers people added earlier; *New password* resets one person. A borrower who signs in
|
||||
sees the library and a **My books** page: what they have out, when each is due (or overdue), **Renew**, and **Change my
|
||||
password**. They cannot lend, edit or see other people. Passwords are Koha's own patron passwords; this stack relaxes Koha's
|
||||
default strength rule (4+ characters instead of 8 with upper/lower/digit) once, at install or update.
|
||||
Ten wrong guesses from one address locks sign-in attempts from it for five minutes.
|
||||
|
||||
### Reading levels: pop-up or in-page AR BookFind
|
||||
|
||||
The book page's AR box has **Open beside this page** (a pop-up window next to the book) and **Show it here** (AR BookFind inside the
|
||||
page; some browsers block its cookies inside another page, then use the pop-up). Either copies the title first so you can paste
|
||||
it into AR's search; AR's own page cannot be pre-filled from here.
|
||||
|
||||
### Same ports on every install
|
||||
|
||||
New installs of this stack use the same three ports whether you install it from the kiosk add-on or from `ubuntu-post-install`:
|
||||
**8098** is the one to use (the Room for Books home page, with Koha behind the menu), 8099 is Add books direct, and 8097 is the
|
||||
public catalogue, which stays closed. If one is taken, the next free port is used and the installer prints it. Existing installs
|
||||
keep the ports they already have.
|
||||
|
||||
## HTTPS with Caddy (and the live camera scanner)
|
||||
|
||||
Phones only allow a live camera on an HTTPS page. One command adds Caddy in front of the library:
|
||||
|
||||
```bash
|
||||
# a real domain that points at this machine (ports 80 and 443 reachable): the certificate is automatic
|
||||
sudo ./install.sh --domain library.example.com
|
||||
|
||||
# a name only your own network knows (library.home.lan): Caddy makes its own certificate authority
|
||||
sudo ./install.sh --domain library.home.lan --lan-tls # then install caddy-root.crt on your phones
|
||||
```
|
||||
|
||||
It writes `Caddyfile`, starts a `caddy` container (the compose `caddy` profile), sets `STAFF_URL=https://<name>` and closes the plain-HTTP
|
||||
ports to the network (`BIND_ADDR=127.0.0.1`; add `--keep-direct` to leave them open). Then use `https://<name>/`: everything works
|
||||
as before, and the **Live scan** buttons appear on Add books and Lend / return. Behind your own Caddy instead, point it at the library
|
||||
port: `library.example.com { reverse_proxy <this machine>:8098 }` and set `STAFF_URL=https://library.example.com` in `.env`.
|
||||
|
||||
@@ -35,6 +35,7 @@ services:
|
||||
environment:
|
||||
TZ: ${TZ:-UTC}
|
||||
KOHA_QUICKADD_URL: http://quickadd:8082
|
||||
KOHA_SSO_SECRET: ${SSO_SECRET:-}
|
||||
KOHA_DB_HOST: db
|
||||
KOHA_DB_PASS: ${DB_PASSWORD}
|
||||
KOHA_ADMIN_USER: ${ADMIN_USER:-admin}
|
||||
@@ -45,8 +46,8 @@ services:
|
||||
KOHA_ITEM_TYPES: ${ITEM_TYPES:-}
|
||||
ports:
|
||||
# The public catalogue is off by default (loopback only); set OPAC_BIND=0.0.0.0 in .env to expose it.
|
||||
- "${OPAC_BIND:-127.0.0.1}:${OPAC_PORT:-8080}:80"
|
||||
- "${BIND_ADDR:-0.0.0.0}:${STAFF_PORT:-8081}:8081"
|
||||
- "${OPAC_BIND:-127.0.0.1}:${OPAC_PORT:-8097}:80"
|
||||
- "${BIND_ADDR:-0.0.0.0}:${STAFF_PORT:-8098}:8081"
|
||||
volumes:
|
||||
- koha-data:/var/lib/koha
|
||||
|
||||
@@ -60,22 +61,42 @@ services:
|
||||
environment:
|
||||
TZ: ${TZ:-UTC}
|
||||
KOHA_API: http://koha:8081/api/v1
|
||||
SSO_SECRET: ${SSO_SECRET:-}
|
||||
ADMIN_USER: ${ADMIN_USER:-admin}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
||||
VIEWER_USER: ${VIEWER_USER:-guest}
|
||||
VIEWER_PASSWORD: ${VIEWER_PASSWORD:-}
|
||||
KOHA_LIBRARY: ${LIBRARY_CODE:-HOME}
|
||||
OPAC_PORT: ${OPAC_PORT:-8080}
|
||||
STAFF_PORT: ${STAFF_PORT:-8081}
|
||||
OPAC_PORT: ${OPAC_PORT:-8097}
|
||||
STAFF_PORT: ${STAFF_PORT:-8098}
|
||||
OPAC_URL: ${OPAC_URL:-}
|
||||
STAFF_URL: ${STAFF_URL:-}
|
||||
QUICKADD_SOURCES: ${QUICKADD_SOURCES:-z3950:lx2.loc.gov:210/LCDB,openlibrary}
|
||||
ports:
|
||||
- "${BIND_ADDR:-0.0.0.0}:${QUICKADD_PORT:-8082}:8082"
|
||||
- "${BIND_ADDR:-0.0.0.0}:${QUICKADD_PORT:-8099}:8082"
|
||||
volumes:
|
||||
- quickadd-data:/data
|
||||
|
||||
# HTTPS with an automatic certificate (and so the live camera on phones): only started when
|
||||
# `./install.sh --domain <name>` has set COMPOSE_PROFILES=caddy in .env.
|
||||
caddy:
|
||||
profiles: ["caddy"]
|
||||
image: caddy:2
|
||||
container_name: library-caddy
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- koha
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
koha-data:
|
||||
quickadd-data:
|
||||
caddy-data:
|
||||
caddy-config:
|
||||
|
||||
+71
-6
@@ -5,13 +5,16 @@
|
||||
# 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
|
||||
# sudo ./install.sh --domain library.example.com # HTTPS via Caddy (certificate is automatic)
|
||||
# sudo ./install.sh --domain library.home.lan --lan-tls # HTTPS for a name only your network knows (own CA)
|
||||
# add --keep-direct to also keep the plain-HTTP ports open on the network
|
||||
#
|
||||
# Optional environment variables:
|
||||
# LIBRARY_NAME="Smith Family Library" name shown on the catalogue
|
||||
# LIBRARY_CODE=HOME short code for the library (letters/digits)
|
||||
# SHELVES="LR=Living room;BR=Bedroom" shelves/rooms to offer (first install only)
|
||||
# ITEM_TYPES="CD=Compact disc" extra item types (first install only)
|
||||
# OPAC_PORT=8080 STAFF_PORT=8081 preferred host ports (next free is used)
|
||||
# OPAC_PORT=8097 STAFF_PORT=8098 preferred host ports (next free is used)
|
||||
# EXTRA_CA=/path/to/proxy-ca.crt trust an extra CA while building
|
||||
set -euo pipefail
|
||||
|
||||
@@ -79,6 +82,10 @@ show_status() {
|
||||
fi
|
||||
dc ps
|
||||
echo
|
||||
if [ -n "${DOMAIN:-}" ]; then
|
||||
echo " Your library (HTTPS): https://$DOMAIN <- use this one: the live camera scanner needs it"
|
||||
echo " (Koha's own screens: https://$DOMAIN/cgi-bin/koha/mainpage.pl, or the menu inside)"
|
||||
fi
|
||||
echo " Your library: http://$ip:$STAFF_PORT"
|
||||
echo " sign in here: find a book, add books (scan an ISBN or use the phone camera), lending, labels, people"
|
||||
echo " Admin login: $ADMIN_USER / $ADMIN_PASSWORD (also stored in $HERE/.env)"
|
||||
@@ -90,6 +97,52 @@ show_status() {
|
||||
echo
|
||||
}
|
||||
|
||||
# --- HTTPS (Caddy) options --------------------------------------------------------
|
||||
DOMAIN_ARG="" LAN_TLS=0 KEEP_DIRECT=0 ACTION=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--domain) DOMAIN_ARG="${2:-}"; [ -n "$DOMAIN_ARG" ] || { c_err "--domain needs a name"; exit 1; }; shift 2 ;;
|
||||
--lan-tls) LAN_TLS=1; shift ;;
|
||||
--keep-direct) KEEP_DIRECT=1; shift ;;
|
||||
*) ACTION="${ACTION:+$ACTION }$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
set -- $ACTION
|
||||
if [ -n "$DOMAIN_ARG" ] && ! printf '%s' "$DOMAIN_ARG" | grep -Eq '^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?$'; then
|
||||
c_err "That does not look like a host name: $DOMAIN_ARG"; exit 1
|
||||
fi
|
||||
|
||||
# Write (or replace) KEY=VALUE in .env
|
||||
env_set() { if grep -q "^$1=" .env; then sed -i "s|^$1=.*|$1=$2|" .env; else echo "$1=$2" >> .env; fi; }
|
||||
|
||||
configure_caddy() {
|
||||
local name="$DOMAIN_ARG"
|
||||
# Caddy needs 80 and 443 (it is not moved to other ports: browsers expect these).
|
||||
if ! dc ps --status running --services 2>/dev/null | grep -qx caddy; then
|
||||
for p in 80 443; do
|
||||
port_in_use "$p" && { c_err "Port $p is already in use on this machine - Caddy needs it. Stop what is using it, or use your own reverse proxy (see README)."; exit 1; }
|
||||
done
|
||||
fi
|
||||
{
|
||||
echo "# Generated by install.sh --domain - Caddy fetches and renews the certificate by itself."
|
||||
echo "$name {"
|
||||
[ "$LAN_TLS" = 1 ] && echo " tls internal"
|
||||
echo " encode zstd gzip"
|
||||
echo " reverse_proxy koha:8081"
|
||||
echo "}"
|
||||
} > Caddyfile
|
||||
env_set DOMAIN "$name"
|
||||
env_set COMPOSE_PROFILES caddy
|
||||
env_set STAFF_URL "https://$name"
|
||||
[ "$KEEP_DIRECT" = 1 ] || env_set BIND_ADDR 127.0.0.1
|
||||
c_ok "Caddy will serve https://$name"
|
||||
if [ "$LAN_TLS" = 1 ]; then
|
||||
c_info "Own-CA certificate: phones must trust Caddy's root, which is copied to $HERE/caddy-root.crt after start."
|
||||
else
|
||||
c_info "The name must point at this machine (DNS) and ports 80/443 must reach it, or the certificate cannot be issued."
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--status) show_status; exit 0 ;;
|
||||
--stop) dc stop; exit 0 ;;
|
||||
@@ -117,9 +170,9 @@ LIBRARY_CODE=${LIBRARY_CODE:-HOME}
|
||||
SHELVES="${SHELVES:-}"
|
||||
ITEM_TYPES="${ITEM_TYPES:-}"
|
||||
TZ=${TZ_NOW}
|
||||
OPAC_PORT=$(free_port "${OPAC_PORT:-8080}")
|
||||
STAFF_PORT=$(free_port "${STAFF_PORT:-8081}")
|
||||
QUICKADD_PORT=$(free_port "${QUICKADD_PORT:-8082}")
|
||||
OPAC_PORT=$(free_port "${OPAC_PORT:-8097}")
|
||||
STAFF_PORT=$(free_port "${STAFF_PORT:-8098}")
|
||||
QUICKADD_PORT=$(free_port "${QUICKADD_PORT:-8099}")
|
||||
BIND_ADDR=${BIND_ADDR:-0.0.0.0}
|
||||
# The public catalogue is off by default: 127.0.0.1 = not reachable from the network.
|
||||
OPAC_BIND=${OPAC_BIND:-127.0.0.1}
|
||||
@@ -127,6 +180,8 @@ ADMIN_USER=admin
|
||||
ADMIN_PASSWORD=$(rand 16)
|
||||
VIEWER_USER=guest
|
||||
VIEWER_PASSWORD=$(rand 14)
|
||||
# Lets the library site vouch for a signed-in staff user to the Add books page (no second login).
|
||||
SSO_SECRET=$(rand 40)
|
||||
DB_ROOT_PASSWORD=$(rand 32)
|
||||
DB_PASSWORD=$(rand 32)
|
||||
KOHA_SUITE=stable
|
||||
@@ -137,11 +192,12 @@ 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
|
||||
echo "QUICKADD_PORT=$(free_port 8099)" >> .env
|
||||
fi
|
||||
if ! grep -q '^VIEWER_PASSWORD=' .env; then
|
||||
{ echo "VIEWER_USER=guest"; echo "VIEWER_PASSWORD=$(rand 14)"; } >> .env
|
||||
fi
|
||||
grep -q '^SSO_SECRET=' .env || echo "SSO_SECRET=$(rand 40)" >> .env
|
||||
# Existing installs keep their current public-catalogue behaviour.
|
||||
grep -q '^OPAC_BIND=' .env || echo "OPAC_BIND=0.0.0.0" >> .env
|
||||
if ! grep -q '^BIND_ADDR=' .env; then
|
||||
@@ -149,6 +205,8 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
[ -n "$DOMAIN_ARG" ] && configure_caddy
|
||||
|
||||
if [ -n "${EXTRA_CA:-}" ]; then
|
||||
cp "$EXTRA_CA" koha/certs/extra-ca.crt
|
||||
cp "$EXTRA_CA" quickadd/certs/extra-ca.crt
|
||||
@@ -167,7 +225,7 @@ with_retry dc up -d
|
||||
c_info "First start installs the database - waiting until Koha reports healthy..."
|
||||
for i in $(seq 1 180); do
|
||||
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' library-koha 2>/dev/null || echo missing)"
|
||||
if [ "$health" = healthy ] && curl -fsS -o /dev/null -m 5 "http://127.0.0.1:${QUICKADD_PORT:-8082}/healthz" 2>/dev/null; then READY=1; break; fi
|
||||
if [ "$health" = healthy ] && curl -fsS -o /dev/null -m 5 "http://127.0.0.1:${QUICKADD_PORT:-8099}/healthz" 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
|
||||
@@ -177,5 +235,12 @@ done
|
||||
[ "${READY:-0}" = 1 ] || { c_err "Timed out - see: docker logs -f library-koha"; exit 1; }
|
||||
|
||||
c_ok "Your library is running."
|
||||
if [ -n "${DOMAIN:-}" ] && grep -q '^ tls internal' Caddyfile 2>/dev/null; then
|
||||
for i in $(seq 1 20); do
|
||||
docker cp library-caddy:/data/caddy/pki/authorities/local/root.crt "$HERE/caddy-root.crt" >/dev/null 2>&1 && break
|
||||
sleep 2
|
||||
done
|
||||
[ -f "$HERE/caddy-root.crt" ] && c_info "Install this certificate on phones/computers that should trust https://$DOMAIN: $HERE/caddy-root.crt"
|
||||
fi
|
||||
echo
|
||||
show_status
|
||||
|
||||
@@ -39,6 +39,12 @@ RUN for c in koha-create koha-plack koha-zebra koha-rebuild-zebra koha-upgrade-s
|
||||
command -v "$c" >/dev/null || { echo "BUILD ERROR: $c is missing from the image" >&2; exit 1; }; \
|
||||
done
|
||||
|
||||
# The endpoint Quick Add uses to recognise a signed-in staff user (see the file header).
|
||||
COPY quickadd-session.pl /usr/share/koha/intranet/cgi-bin/svc/quickadd-session
|
||||
# (the return script lives beside the staff .pl scripts, not under svc/: Koha's svc Plack app 500s on it)
|
||||
COPY quickadd-return.pl quickadd-value.pl /usr/share/koha/intranet/cgi-bin/
|
||||
RUN chmod 0755 /usr/share/koha/intranet/cgi-bin/svc/quickadd-session /usr/share/koha/intranet/cgi-bin/quickadd-return.pl /usr/share/koha/intranet/cgi-bin/quickadd-value.pl
|
||||
|
||||
COPY bootstrap.pl ensure.pl entrypoint.sh /opt/koha-init/
|
||||
COPY seed/ /opt/koha-init/seed/
|
||||
RUN chmod 0755 /opt/koha-init/entrypoint.sh && chmod -R a+rX /opt/koha-init
|
||||
|
||||
+29
-9
@@ -7,6 +7,7 @@ use Modern::Perl;
|
||||
use C4::Context;
|
||||
use Koha::Patrons;
|
||||
use Koha::Patron::Categories;
|
||||
use Koha::Caches;
|
||||
|
||||
my $dbh = C4::Context->dbh;
|
||||
# The read-only "guest" login lives in Quick Add only (the Find a book page), NOT in Koha:
|
||||
@@ -20,16 +21,35 @@ if ( my $cat = Koha::Patron::Categories->find('VIEWER') ) {
|
||||
$cat->delete unless Koha::Patrons->search( { categorycode => 'VIEWER' } )->count;
|
||||
}
|
||||
|
||||
# Staff home page block (Koha 26.05 keeps these in the HTML customizations tool).
|
||||
my ($have) = $dbh->selectrow_array(q{SELECT COUNT(*) FROM additional_contents WHERE code = 'library_tools'});
|
||||
unless ($have) {
|
||||
my $html = '<div class="main-heading"><h3>Library tools</h3></div><ul class="list-unstyled">'
|
||||
. '<li><a class="btn btn-primary" href="/add/#find">Find a book</a></li>'
|
||||
. '<li><a href="/add/">Add books (scan an ISBN)</a></li></ul>';
|
||||
# Patron logins use short, easy-to-remember passwords (a home library, not a bank): Koha's defaults
|
||||
# (8+ characters, upper+lower+digit) are relaxed once, unless an admin has changed them since.
|
||||
my $flag = '/var/lib/koha/' . ( $ENV{KOHA_INSTANCE} || 'library' ) . '/.password-policy-set';
|
||||
unless ( -e $flag ) {
|
||||
my ($strong) = $dbh->selectrow_array(q{SELECT value FROM systempreferences WHERE variable = 'RequireStrongPassword'});
|
||||
my ($minlen) = $dbh->selectrow_array(q{SELECT value FROM systempreferences WHERE variable = 'minPasswordLength'});
|
||||
if ( ( $strong // 1 ) == 1 && ( $minlen // 8 ) == 8 ) {
|
||||
$dbh->do(q{UPDATE systempreferences SET value = 0 WHERE variable = 'RequireStrongPassword'});
|
||||
$dbh->do(q{UPDATE systempreferences SET value = 4 WHERE variable = 'minPasswordLength'});
|
||||
say "ensure: patron passwords may be short (4+ characters, no strength rule)";
|
||||
Koha::Caches->get_instance->flush_all; # preferences are cached; direct SQL edits need this
|
||||
}
|
||||
if ( open my $fh, '>', $flag ) { close $fh }
|
||||
}
|
||||
|
||||
# Staff home page block (Koha 26.05 keeps these in the HTML customizations tool). Rewritten each start.
|
||||
my $html = '<div class="main-heading"><h3>Room for Books</h3></div><ul class="list-unstyled">'
|
||||
. '<li><a class="btn btn-primary" href="/add/">Open Room for Books</a> (library, lend / return, people, add books)</li>'
|
||||
. '<li><a href="/add/#find">Find a book</a></li></ul>';
|
||||
my ($id) = $dbh->selectrow_array(q{SELECT id FROM additional_contents WHERE code = 'library_tools'});
|
||||
unless ($id) {
|
||||
$dbh->do(q{INSERT INTO additional_contents (category, code, location, branchcode, published_on, number)
|
||||
VALUES ('html_customizations', 'library_tools', 'IntranetmainUserblock', NULL, CURDATE(), 1)});
|
||||
my $id = $dbh->last_insert_id( undef, undef, 'additional_contents', 'id' );
|
||||
$id = $dbh->last_insert_id( undef, undef, 'additional_contents', 'id' );
|
||||
$dbh->do( q{INSERT INTO additional_contents_localizations (additional_content_id, title, content, lang)
|
||||
VALUES (?, 'Library tools', ?, 'default')}, undef, $id, $html );
|
||||
say "ensure: added the staff home 'Library tools' block";
|
||||
VALUES (?, 'Room for Books', ?, 'default')}, undef, $id, $html );
|
||||
say "ensure: added the staff home 'Room for Books' block";
|
||||
}
|
||||
else {
|
||||
$dbh->do( q{UPDATE additional_contents_localizations SET title = 'Room for Books', content = ? WHERE additional_content_id = ? AND lang = 'default'},
|
||||
undef, $html, $id );
|
||||
}
|
||||
|
||||
@@ -60,22 +60,39 @@ if [ ! -d "/etc/koha/sites/$INST" ]; then
|
||||
# (The staff and public catalogue sites cannot share one address: Koha gives both the
|
||||
# same URL shapes. The staff site is the natural home for a home library.)
|
||||
if [ -n "${KOHA_QUICKADD_URL:-}" ]; then
|
||||
# Single sign-on for staff: when KOHA_SSO_SECRET is set, this web server tells Quick Add (via a
|
||||
# secret header nobody outside can forge: it is overwritten here on every request) that the
|
||||
# request came through the staff site, and Quick Add then asks Koha whether the browser's login
|
||||
# cookie belongs to a staff user who may edit the catalogue. Without the secret, or if anything
|
||||
# about that fails, Quick Add just asks for its own login as before.
|
||||
SSO_BLOCK=""
|
||||
if [ -n "${KOHA_SSO_SECRET:-}" ]; then
|
||||
install -m 0640 -o root -g "$INST-koha" /dev/null /etc/koha/quickadd-sso.secret
|
||||
printf '%s' "$KOHA_SSO_SECRET" > /etc/koha/quickadd-sso.secret
|
||||
SSO_BLOCK=" <IfModule mod_headers.c>
|
||||
<Location /add/>
|
||||
RequestHeader set X-Quickadd-Proxy \"$KOHA_SSO_SECRET\"
|
||||
</Location>
|
||||
</IfModule>"
|
||||
fi
|
||||
cat > /etc/koha/apache-quickadd.conf << APACHE
|
||||
<IfModule mod_proxy_http.c>
|
||||
RedirectMatch ^/add\$ /add/
|
||||
# The bare address (http://ip:port/ or https://your-domain/) opens the Room for Books home page;
|
||||
# Koha's own staff screens stay at /cgi-bin/koha/mainpage.pl.
|
||||
RedirectMatch ^/\$ /add/
|
||||
ProxyPass /add/ ${KOHA_QUICKADD_URL%/}/
|
||||
ProxyPassReverse /add/ ${KOHA_QUICKADD_URL%/}/
|
||||
${SSO_BLOCK}
|
||||
</IfModule>
|
||||
APACHE
|
||||
sed -i 's|^\(\s*\)Include /etc/koha/apache-shared-intranet.conf|&\n\1Include /etc/koha/apache-quickadd.conf|' "/etc/apache2/sites-available/$INST.conf"
|
||||
fi
|
||||
|
||||
# Run Apache itself as the instance user instead of mpm_itk's per-vhost
|
||||
# uid switching (which needs extra capabilities and is pointless here:
|
||||
# Plack does the real work as that user already).
|
||||
# koha-create insists on mpm_itk being enabled, but inside a container it can
|
||||
# not switch users (initgroups: Operation not permitted -> HTTP 500 on every
|
||||
# page). Now that the instance exists, swap to plain prefork.
|
||||
# page). Now that the instance exists, swap to plain prefork; Apache runs as the
|
||||
# instance user (Plack does the real work as that user already).
|
||||
rm -f /etc/apache2/mods-enabled/mpm_itk.*
|
||||
a2enmod mpm_prefork >/dev/null 2>&1 || true
|
||||
sed -i '/AssignUserID/d' "$SITE"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/perl
|
||||
# Installed as .../intranet/cgi-bin/quickadd-return.pl (URL /cgi-bin/koha/quickadd-return.pl).
|
||||
#
|
||||
# Koha's REST API has no "check in" call, so the Add books / Lend page asks this small script to do it
|
||||
# with Koha's own C4::Circulation::AddReturn (the same code the staff Check in screen runs: it clears
|
||||
# the loan, handles holds and fines rules). Same shared-secret guard as quickadd-session: only callers
|
||||
# that know the secret get an answer.
|
||||
use Modern::Perl;
|
||||
use CGI qw( -utf8 );
|
||||
use JSON qw( encode_json );
|
||||
use Digest::SHA qw( sha256_hex );
|
||||
use C4::Context;
|
||||
use C4::Circulation qw( AddReturn );
|
||||
use Koha::Items;
|
||||
|
||||
my $q = CGI->new;
|
||||
|
||||
sub reply {
|
||||
my ( $q, $status, $data ) = @_;
|
||||
print $q->header( -type => 'application/json', -status => $status, -charset => 'utf-8', -cache_control => 'no-store' ),
|
||||
encode_json($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
my $secret = '';
|
||||
if ( open my $fh, '<', '/etc/koha/quickadd-sso.secret' ) { $secret = <$fh> // ''; $secret =~ s/\s+$//; close $fh }
|
||||
my $given = $ENV{HTTP_X_QUICKADD_SECRET} // '';
|
||||
reply( $q, '403 Forbidden', { status => 'forbidden' } )
|
||||
unless length($secret) >= 16 && sha256_hex($given) eq sha256_hex($secret);
|
||||
|
||||
my $barcode = $q->param('barcode') // '';
|
||||
reply( $q, '200 OK', { status => 'invalid' } ) unless $barcode =~ /\A[\x21-\x7e]{1,40}\z/;
|
||||
|
||||
my $item = Koha::Items->find( { barcode => $barcode } );
|
||||
reply( $q, '200 OK', { status => 'unknown_barcode' } ) unless $item;
|
||||
|
||||
# AddReturn needs to know which branch it is happening at.
|
||||
my $branch = $item->homebranch;
|
||||
my ( $doreturn, $messages, $borrower, $was_out );
|
||||
eval {
|
||||
C4::Context->set_userenv( 0, 0, 'quickadd', 'Quick', 'Add', $branch, $branch, 0, '', '' );
|
||||
$was_out = $item->checkout ? 1 : 0;
|
||||
( $doreturn, $messages, undef, $borrower ) = AddReturn( $barcode, $branch );
|
||||
1;
|
||||
} or reply( $q, '200 OK', { status => 'failed', error => "$@" } );
|
||||
reply(
|
||||
$q, '200 OK',
|
||||
{
|
||||
status => $doreturn && $was_out ? 'returned' : ( $doreturn || exists $messages->{NotIssued} ) ? 'not_out' : 'failed',
|
||||
patron => $borrower ? ( join ' ', grep { length } $borrower->{firstname}, $borrower->{surname} ) : '',
|
||||
messages => [ sort keys %{ $messages || {} } ],
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/perl
|
||||
# Installed as .../intranet/cgi-bin/svc/quickadd-session (URL /cgi-bin/koha/svc/quickadd-session).
|
||||
#
|
||||
# Lets the Add books page (Quick Add) ask: "is this browser signed in to the staff site as
|
||||
# someone allowed to edit the catalogue?" so staff do not have to log in a second time.
|
||||
#
|
||||
# It wraps Koha's own C4::Auth::check_cookie_auth (documented for exactly this: services that
|
||||
# are invoked by pages authenticated in the usual way) and only answers callers that present a
|
||||
# shared secret, so it cannot be used to probe sessions from outside. The browser's address is
|
||||
# passed in explicitly, so Koha's "session is tied to the IP that logged in" rule keeps working.
|
||||
use Modern::Perl;
|
||||
use CGI qw( -utf8 );
|
||||
use JSON qw( encode_json );
|
||||
use Digest::SHA qw( sha256_hex );
|
||||
use C4::Auth qw( check_cookie_auth );
|
||||
|
||||
my $q = CGI->new;
|
||||
|
||||
# Takes the CGI object as an argument: under Plack this file runs wrapped in a sub, where a named
|
||||
# sub would otherwise see a stale copy of a file-level variable.
|
||||
sub reply {
|
||||
my ( $q, $status, $data ) = @_;
|
||||
print $q->header( -type => 'application/json', -status => $status, -charset => 'utf-8', -cache_control => 'no-store' ),
|
||||
encode_json($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
# 1. The caller must know the shared secret (a file only the Koha user and root can read).
|
||||
my $secret = '';
|
||||
if ( open my $fh, '<', '/etc/koha/quickadd-sso.secret' ) { $secret = <$fh> // ''; $secret =~ s/\s+$//; close $fh }
|
||||
my $given = $ENV{HTTP_X_QUICKADD_SECRET} // '';
|
||||
reply( $q, '403 Forbidden', { status => 'forbidden' } )
|
||||
unless length($secret) >= 16 && sha256_hex($given) eq sha256_hex($secret);
|
||||
|
||||
# 2. Plausible-looking inputs only.
|
||||
my $sid = $q->param('sid') // '';
|
||||
my $ip = $q->param('ip') // '';
|
||||
reply( $q, '200 OK', { status => 'failed' } )
|
||||
unless $sid =~ /\A[A-Za-z0-9]{16,64}\z/ && $ip =~ /\A[0-9A-Fa-f:.]{3,45}\z/;
|
||||
|
||||
# 3. Ask Koha. Staff who may edit the catalogue (superlibrarians always may) are let in.
|
||||
my ( $status, $session ) = check_cookie_auth( $sid, { editcatalogue => 'edit_catalogue' }, { remote_addr => $ip } );
|
||||
reply( $q, '200 OK', { status => 'ok', user => scalar $session->param('id') } ) if $status eq 'ok' && $session;
|
||||
reply( $q, '200 OK', { status => $status || 'failed' } );
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/perl
|
||||
# Installed as .../intranet/cgi-bin/quickadd-value.pl (URL /cgi-bin/koha/quickadd-value.pl).
|
||||
#
|
||||
# Koha's REST API cannot add shelves/rooms (authorised value category LOC) or collections (CCODE),
|
||||
# so the Library site asks this script to, with Koha's own Koha::AuthorisedValue class. Same shared-secret
|
||||
# guard as quickadd-session / quickadd-return; only the two categories below can be touched.
|
||||
use Modern::Perl;
|
||||
use CGI qw( -utf8 );
|
||||
use JSON qw( encode_json );
|
||||
use Digest::SHA qw( sha256_hex );
|
||||
use Koha::AuthorisedValues;
|
||||
use Koha::AuthorisedValue;
|
||||
|
||||
my $q = CGI->new;
|
||||
|
||||
sub reply {
|
||||
my ( $q, $status, $data ) = @_;
|
||||
print $q->header( -type => 'application/json', -status => $status, -charset => 'utf-8', -cache_control => 'no-store' ),
|
||||
encode_json($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
my $secret = '';
|
||||
if ( open my $fh, '<', '/etc/koha/quickadd-sso.secret' ) { $secret = <$fh> // ''; $secret =~ s/\s+$//; close $fh }
|
||||
my $given = $ENV{HTTP_X_QUICKADD_SECRET} // '';
|
||||
reply( $q, '403 Forbidden', { status => 'forbidden' } )
|
||||
unless length($secret) >= 16 && sha256_hex($given) eq sha256_hex($secret);
|
||||
|
||||
my $cat = $q->param('category') // '';
|
||||
my $value = $q->param('value') // '';
|
||||
my $desc = $q->param('description') // '';
|
||||
utf8::decode($desc);
|
||||
reply( $q, '200 OK', { status => 'invalid' } )
|
||||
unless $cat =~ /\A(?:LOC|CCODE)\z/ && $value =~ /\A[A-Z0-9_-]{1,20}\z/ && length($desc) && length($desc) <= 80;
|
||||
|
||||
my $out = eval {
|
||||
my $exists = Koha::AuthorisedValues->search( { category => $cat, authorised_value => $value } )->count;
|
||||
return { status => 'exists' } if $exists;
|
||||
Koha::AuthorisedValue->new( { category => $cat, authorised_value => $value, lib => $desc, lib_opac => $desc } )->store;
|
||||
{ status => 'added' };
|
||||
};
|
||||
reply( $q, '200 OK', $out // { status => 'failed', error => "$@" } );
|
||||
@@ -5,7 +5,7 @@ FROM debian:bookworm-slim
|
||||
ENV DEBIAN_FRONTEND=noninteractive PYTHONUNBUFFERED=1
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates python3 yaz zbar-tools tzdata \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates python3 yaz zbar-tools qrencode tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Optional: drop extra CA certificates (*.crt) into ./certs/ before building if
|
||||
@@ -13,7 +13,7 @@ RUN apt-get update \
|
||||
COPY certs/ /usr/local/share/ca-certificates/extra/
|
||||
RUN update-ca-certificates
|
||||
|
||||
RUN command -v yaz-client >/dev/null && command -v yaz-marcdump >/dev/null && command -v python3 >/dev/null && command -v zbarimg >/dev/null
|
||||
RUN command -v yaz-client >/dev/null && command -v yaz-marcdump >/dev/null && command -v python3 >/dev/null && command -v zbarimg >/dev/null && command -v qrencode >/dev/null
|
||||
|
||||
RUN useradd --system --uid 10001 --home-dir /app quickadd \
|
||||
&& mkdir -p /app /data && chown quickadd /data
|
||||
|
||||
Binary file not shown.
+1054
-12
File diff suppressed because it is too large
Load Diff
+919
-51
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user