Compare commits
13
Commits
886745a979
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6532c1367 | ||
|
|
6fdc5f4dd1 | ||
|
|
0af833a39d | ||
|
|
442b45c2e0 | ||
|
|
6f877d1107 | ||
|
|
3d02d28a8c | ||
|
|
7f230c8e73 | ||
|
|
b784d98c9d | ||
|
|
d01919e2ac | ||
|
|
10e991e158 | ||
|
|
9eadf266f9 | ||
|
|
770a6a1dfb | ||
|
|
522a33b127 |
+94
-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:
|
A self-hosted library catalogue you can set up in one command:
|
||||||
|
|
||||||
@@ -12,12 +12,19 @@ it never overwrites your `.env` (passwords) or your data.
|
|||||||
|
|
||||||
| What | Address | Login |
|
| What | Address | Login |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Catalogue** (public search, no login) | `http://<this machine>:8080` | none |
|
| **Your library**: add books, find a book, people, lending, labels, settings | `http://<this machine>:8081` | `admin` and the generated password |
|
||||||
| **Staff interface** (cataloguing, labels, Z39.50) | `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) |
|
||||||
| **Add books** (scan an ISBN) | `http://<this machine>:8082` | same login |
|
| **Public catalogue** (search only, for a kiosk or visitors) | `http://<this machine>:8080` | 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
|
||||||
|
shelf and whether it is on the shelf or checked out) and a **Manage** link back to the staff site. The
|
||||||
|
public catalogue exists for a kiosk screen or visitors and does not need a login. (Koha gives its public and
|
||||||
|
staff sites the same URL shapes, so they cannot share one address; Add books can, and does.)
|
||||||
|
|
||||||
Ports move up to the next free number if one is taken; the final addresses are printed
|
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`.
|
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.
|
||||||
|
|
||||||
## What it is made of
|
## What it is made of
|
||||||
|
|
||||||
@@ -196,3 +203,85 @@ koha/ Dockerfile (Koha from the project's Debian repo), entrypoi
|
|||||||
servers, label templates/layouts)
|
servers, label templates/layouts)
|
||||||
quickadd/ the scan-to-add service (Python standard library + yaz)
|
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 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.
|
||||||
|
|||||||
@@ -30,8 +30,12 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
quickadd:
|
||||||
|
condition: service_started
|
||||||
environment:
|
environment:
|
||||||
TZ: ${TZ:-UTC}
|
TZ: ${TZ:-UTC}
|
||||||
|
KOHA_QUICKADD_URL: http://quickadd:8082
|
||||||
|
KOHA_SSO_SECRET: ${SSO_SECRET:-}
|
||||||
KOHA_DB_HOST: db
|
KOHA_DB_HOST: db
|
||||||
KOHA_DB_PASS: ${DB_PASSWORD}
|
KOHA_DB_PASS: ${DB_PASSWORD}
|
||||||
KOHA_ADMIN_USER: ${ADMIN_USER:-admin}
|
KOHA_ADMIN_USER: ${ADMIN_USER:-admin}
|
||||||
@@ -41,7 +45,8 @@ services:
|
|||||||
KOHA_SHELVES: ${SHELVES:-}
|
KOHA_SHELVES: ${SHELVES:-}
|
||||||
KOHA_ITEM_TYPES: ${ITEM_TYPES:-}
|
KOHA_ITEM_TYPES: ${ITEM_TYPES:-}
|
||||||
ports:
|
ports:
|
||||||
- "${BIND_ADDR:-0.0.0.0}:${OPAC_PORT:-8080}:80"
|
# 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"
|
- "${BIND_ADDR:-0.0.0.0}:${STAFF_PORT:-8081}:8081"
|
||||||
volumes:
|
volumes:
|
||||||
- koha-data:/var/lib/koha
|
- koha-data:/var/lib/koha
|
||||||
@@ -53,14 +58,14 @@ services:
|
|||||||
image: library-quickadd:local
|
image: library-quickadd:local
|
||||||
container_name: library-quickadd
|
container_name: library-quickadd
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
|
||||||
koha:
|
|
||||||
condition: service_started
|
|
||||||
environment:
|
environment:
|
||||||
TZ: ${TZ:-UTC}
|
TZ: ${TZ:-UTC}
|
||||||
KOHA_API: http://koha:8081/api/v1
|
KOHA_API: http://koha:8081/api/v1
|
||||||
|
SSO_SECRET: ${SSO_SECRET:-}
|
||||||
ADMIN_USER: ${ADMIN_USER:-admin}
|
ADMIN_USER: ${ADMIN_USER:-admin}
|
||||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
||||||
|
VIEWER_USER: ${VIEWER_USER:-guest}
|
||||||
|
VIEWER_PASSWORD: ${VIEWER_PASSWORD:-}
|
||||||
KOHA_LIBRARY: ${LIBRARY_CODE:-HOME}
|
KOHA_LIBRARY: ${LIBRARY_CODE:-HOME}
|
||||||
OPAC_PORT: ${OPAC_PORT:-8080}
|
OPAC_PORT: ${OPAC_PORT:-8080}
|
||||||
STAFF_PORT: ${STAFF_PORT:-8081}
|
STAFF_PORT: ${STAFF_PORT:-8081}
|
||||||
|
|||||||
+20
-4
@@ -79,10 +79,14 @@ show_status() {
|
|||||||
fi
|
fi
|
||||||
dc ps
|
dc ps
|
||||||
echo
|
echo
|
||||||
echo " Catalogue (public): http://$ip:$OPAC_PORT"
|
echo " Your library: http://$ip:$STAFF_PORT"
|
||||||
echo " Staff interface: 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 " Add books (scan): http://$ip:${QUICKADD_PORT:-8082} (same login)"
|
echo " Admin login: $ADMIN_USER / $ADMIN_PASSWORD (also stored in $HERE/.env)"
|
||||||
echo " Staff login: $ADMIN_USER / $ADMIN_PASSWORD (also stored in $HERE/.env)"
|
echo " Read-only login: ${VIEWER_USER:-guest} / ${VIEWER_PASSWORD:-?} (to hand out, with this address: http://$ip:$STAFF_PORT/add/)"
|
||||||
|
echo " It opens the Find a book page: search only, it cannot add or change anything."
|
||||||
|
if [ "${OPAC_BIND:-127.0.0.1}" != "127.0.0.1" ]; then
|
||||||
|
echo " Public catalogue: http://$ip:$OPAC_PORT (search only, asks for a library card login)"
|
||||||
|
fi
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,8 +121,14 @@ OPAC_PORT=$(free_port "${OPAC_PORT:-8080}")
|
|||||||
STAFF_PORT=$(free_port "${STAFF_PORT:-8081}")
|
STAFF_PORT=$(free_port "${STAFF_PORT:-8081}")
|
||||||
QUICKADD_PORT=$(free_port "${QUICKADD_PORT:-8082}")
|
QUICKADD_PORT=$(free_port "${QUICKADD_PORT:-8082}")
|
||||||
BIND_ADDR=${BIND_ADDR:-0.0.0.0}
|
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}
|
||||||
ADMIN_USER=admin
|
ADMIN_USER=admin
|
||||||
ADMIN_PASSWORD=$(rand 16)
|
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_ROOT_PASSWORD=$(rand 32)
|
||||||
DB_PASSWORD=$(rand 32)
|
DB_PASSWORD=$(rand 32)
|
||||||
KOHA_SUITE=stable
|
KOHA_SUITE=stable
|
||||||
@@ -131,6 +141,12 @@ else
|
|||||||
if ! grep -q '^QUICKADD_PORT=' .env; then
|
if ! grep -q '^QUICKADD_PORT=' .env; then
|
||||||
echo "QUICKADD_PORT=$(free_port 8082)" >> .env
|
echo "QUICKADD_PORT=$(free_port 8082)" >> .env
|
||||||
fi
|
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
|
if ! grep -q '^BIND_ADDR=' .env; then
|
||||||
echo "BIND_ADDR=${BIND_ADDR:-0.0.0.0}" >> .env
|
echo "BIND_ADDR=${BIND_ADDR:-0.0.0.0}" >> .env
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -39,7 +39,13 @@ 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; }; \
|
command -v "$c" >/dev/null || { echo "BUILD ERROR: $c is missing from the image" >&2; exit 1; }; \
|
||||||
done
|
done
|
||||||
|
|
||||||
COPY bootstrap.pl entrypoint.sh /opt/koha-init/
|
# 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/
|
COPY seed/ /opt/koha-init/seed/
|
||||||
RUN chmod 0755 /opt/koha-init/entrypoint.sh && chmod -R a+rX /opt/koha-init
|
RUN chmod 0755 /opt/koha-init/entrypoint.sh && chmod -R a+rX /opt/koha-init
|
||||||
|
|
||||||
|
|||||||
@@ -160,11 +160,11 @@ my %prefs = (
|
|||||||
finesMode => 'off', # a home library does not charge overdue fines
|
finesMode => 'off', # a home library does not charge overdue fines
|
||||||
itemcallnumber => '082ab', # Dewey call number copied from the MARC record
|
itemcallnumber => '082ab', # Dewey call number copied from the MARC record
|
||||||
RESTBasicAuth => 1, # lets the quick-add page use the REST API
|
RESTBasicAuth => 1, # lets the quick-add page use the REST API
|
||||||
|
OpacPublic => 0, # the public catalogue, if ever exposed, asks for a login
|
||||||
OpenLibraryCovers => 1, # cover images in the catalogue
|
OpenLibraryCovers => 1, # cover images in the catalogue
|
||||||
OPACSearchForTitleIn => '',
|
OPACSearchForTitleIn => '',
|
||||||
BiblioDefaultView => 'normal',
|
BiblioDefaultView => 'normal',
|
||||||
OpacPublic => 1,
|
opacuserlogin => 1,
|
||||||
opacuserlogin => 0, # catalogue is browse-only; no patron logins needed
|
|
||||||
OpacMaintenance => 0,
|
OpacMaintenance => 0,
|
||||||
ExpireReservesMaxPickUpDelay => 0,
|
ExpireReservesMaxPickUpDelay => 0,
|
||||||
AllowOfflineCirculation => 0,
|
AllowOfflineCirculation => 0,
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
# Idempotent housekeeping, run on EVERY container start (after the first-time install or
|
||||||
|
# the schema upgrade), so existing installs pick these up too:
|
||||||
|
# * removal of the Koha guest account an earlier version created (the guest login is Quick Add only)
|
||||||
|
# * the "Library tools" block on the staff home page
|
||||||
|
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:
|
||||||
|
# Koha's own "catalogue" permission also allows editing label layouts and templates, so a
|
||||||
|
# Koha account cannot be made truly read-only. Remove any guest account an earlier version created.
|
||||||
|
for my $p ( Koha::Patrons->search( { categorycode => 'VIEWER' } )->as_list ) {
|
||||||
|
say "ensure: removing the old Koha guest account '" . $p->userid . "'";
|
||||||
|
$p->delete;
|
||||||
|
}
|
||||||
|
if ( my $cat = Koha::Patron::Categories->find('VIEWER') ) {
|
||||||
|
$cat->delete unless Koha::Patrons->search( { categorycode => 'VIEWER' } )->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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)});
|
||||||
|
$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 (?, '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 );
|
||||||
|
}
|
||||||
@@ -56,12 +56,40 @@ if [ ! -d "/etc/koha/sites/$INST" ]; then
|
|||||||
-e 's|^#\s*Include /etc/koha/apache-shared-intranet-plack.conf| Include /etc/koha/apache-shared-intranet-plack.conf|' "$SITE"
|
-e 's|^#\s*Include /etc/koha/apache-shared-intranet-plack.conf| Include /etc/koha/apache-shared-intranet-plack.conf|' "$SITE"
|
||||||
koha-plack --enable "$INST" >/dev/null 2>&1 || true # koha-create may already have enabled it
|
koha-plack --enable "$INST" >/dev/null 2>&1 || true # koha-create may already have enabled it
|
||||||
|
|
||||||
# Run Apache itself as the instance user instead of mpm_itk's per-vhost
|
# One address: serve the Add books page (Quick Add) inside the staff site at /add/.
|
||||||
# uid switching (which needs extra capabilities and is pointless here:
|
# (The staff and public catalogue sites cannot share one address: Koha gives both the
|
||||||
# Plack does the real work as that user already).
|
# 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/
|
||||||
|
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
|
||||||
|
|
||||||
# koha-create insists on mpm_itk being enabled, but inside a container it can
|
# 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
|
# 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.*
|
rm -f /etc/apache2/mods-enabled/mpm_itk.*
|
||||||
a2enmod mpm_prefork >/dev/null 2>&1 || true
|
a2enmod mpm_prefork >/dev/null 2>&1 || true
|
||||||
sed -i '/AssignUserID/d' "$SITE"
|
sed -i '/AssignUserID/d' "$SITE"
|
||||||
@@ -94,6 +122,11 @@ else
|
|||||||
FIRST_RUN=0
|
FIRST_RUN=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- 4b. housekeeping that must hold on every start (staff home block, old guest account cleanup)
|
||||||
|
runuser -u "$INST-koha" -- env KOHA_CONF="/etc/koha/sites/$INST/koha-conf.xml" PERL5LIB=/usr/share/koha/lib \
|
||||||
|
perl /opt/koha-init/ensure.pl \
|
||||||
|
|| log "WARNING: ensure.pl reported a problem (see above)"
|
||||||
|
|
||||||
# --- 5. background services (their `daemon` processes die with the container,
|
# --- 5. background services (their `daemon` processes die with the container,
|
||||||
# so they are started explicitly on every start) ------------------------
|
# so they are started explicitly on every start) ------------------------
|
||||||
if [ "$FIRST_RUN" = 1 ] || [ ! -e "/var/lib/koha/$INST/biblios/register" ]; then
|
if [ "$FIRST_RUN" = 1 ] || [ ! -e "/var/lib/koha/$INST/biblios/register" ]; then
|
||||||
|
|||||||
@@ -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
|
ENV DEBIAN_FRONTEND=noninteractive PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
RUN apt-get update \
|
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/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Optional: drop extra CA certificates (*.crt) into ./certs/ before building if
|
# 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/
|
COPY certs/ /usr/local/share/ca-certificates/extra/
|
||||||
RUN update-ca-certificates
|
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 \
|
RUN useradd --system --uid 10001 --home-dir /app quickadd \
|
||||||
&& mkdir -p /app /data && chown quickadd /data
|
&& mkdir -p /app /data && chown quickadd /data
|
||||||
|
|||||||
Binary file not shown.
+1112
-17
File diff suppressed because it is too large
Load Diff
+938
-18
File diff suppressed because it is too large
Load Diff
+37
-27
@@ -1,29 +1,30 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
################################################################################
|
################################################################################
|
||||||
# menus/addon_library.sh - "Library Catalog" addon: a self-hosted library
|
# menus/addon_library.sh - "Library Catalog" addon: a self-hosted library
|
||||||
# catalogue (Koha) running in Docker, shown on the kiosk as a public
|
# catalogue (Koha) running in Docker, with a "Find a book" page on the kiosk.
|
||||||
# search terminal.
|
|
||||||
#
|
#
|
||||||
# What gets installed (all from this checkout's library/ directory, deployed to
|
# What gets installed (all from this checkout's library/ directory, deployed to
|
||||||
# $LIBRARY_DIR - see library/README.md for the stack itself):
|
# $LIBRARY_DIR - see library/README.md for the stack itself):
|
||||||
# - Koha: the catalogue ("OPAC", public search, no login) and the staff
|
# - Koha: cataloguing, Z39.50 lookup, label printing, people and lending.
|
||||||
# interface (cataloguing, Z39.50 lookup, label printing).
|
# - Quick Add: the "scan an ISBN, the book is added" page, with a Find a book tab.
|
||||||
# - Quick Add: a one-box "scan an ISBN, the book is added" page.
|
|
||||||
# - MariaDB.
|
# - MariaDB.
|
||||||
# Docker itself is installed on demand (lib/docker.sh).
|
# Docker itself is installed on demand (lib/docker.sh).
|
||||||
#
|
#
|
||||||
# On the kiosk the catalogue is added as one of the Sites (tabs) with
|
# Everything needs a login. The kiosk shows the "Find a book" page signed in as a
|
||||||
# duration 0 (stay put - it's a search terminal, not a rotating display).
|
# READ-ONLY guest login (Find a book is search only; the guest has no Koha account at
|
||||||
# Staff and Quick Add are meant for the librarian's own device; whether they
|
# all, because Koha's own search permission also allows editing label layouts); the page
|
||||||
# are reachable from the LAN or only from this kiosk is the "Network access"
|
# list stores that guest login like any other Basic Auth site. The admin login never
|
||||||
# choice below. NOTE: Docker publishes ports straight through iptables and
|
# goes into the kiosk's config. The public catalogue (OPAC) is off by default.
|
||||||
# bypasses ufw, so "this kiosk only" is enforced by binding the published
|
#
|
||||||
# ports to 127.0.0.1 (BIND_ADDR in library/.env), not by firewall rules.
|
# Whether other devices on the LAN can reach the library site is the "Network access"
|
||||||
|
# choice below. NOTE: Docker publishes ports straight through iptables and bypasses
|
||||||
|
# ufw, so "this kiosk only" is enforced by binding the published ports to 127.0.0.1
|
||||||
|
# (BIND_ADDR in library/.env), not by firewall rules.
|
||||||
#
|
#
|
||||||
# Depends on: lib/menu.sh, lib/config.sh, lib/docker.sh being sourced first.
|
# Depends on: lib/menu.sh, lib/config.sh, lib/docker.sh being sourced first.
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
LIBRARY_CATALOGUE_TAB_NAME="Library catalogue"
|
LIBRARY_CATALOGUE_TAB_NAME="Find a book"
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Detection / state
|
# Detection / state
|
||||||
@@ -42,7 +43,13 @@ library_is_running() {
|
|||||||
[[ "$(docker_container_state library-koha)" == "running" ]]
|
[[ "$(docker_container_state library-koha)" == "running" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The page the kiosk shows: Quick Add's Find a book view on the library site.
|
||||||
library_catalogue_url() {
|
library_catalogue_url() {
|
||||||
|
echo "http://localhost:$(library_env_get STAFF_PORT)/add/#find"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pages from earlier versions of this add-on pointed at the public catalogue.
|
||||||
|
library_old_catalogue_url() {
|
||||||
echo "http://localhost:$(library_env_get OPAC_PORT)/"
|
echo "http://localhost:$(library_env_get OPAC_PORT)/"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,19 +73,19 @@ addon_library_status() {
|
|||||||
health="$(docker_container_health library-koha)"
|
health="$(docker_container_health library-koha)"
|
||||||
ip="$(get_ip_address)"
|
ip="$(get_ip_address)"
|
||||||
echo "Library Catalog: ${state}${health:+ ($health)}"
|
echo "Library Catalog: ${state}${health:+ ($health)}"
|
||||||
echo " Catalogue: http://localhost:$(library_env_get OPAC_PORT)/ (shown on the kiosk)"
|
echo " Find a book: $(library_catalogue_url) (shown on the kiosk, as the read-only guest)"
|
||||||
if [[ "$(library_env_get BIND_ADDR)" == "127.0.0.1" ]]; then
|
if [[ "$(library_env_get BIND_ADDR)" == "127.0.0.1" ]]; then
|
||||||
echo " Network: this kiosk only"
|
echo " Network: this kiosk only"
|
||||||
else
|
else
|
||||||
echo " Network: other devices on the LAN can reach it at http://${ip}:$(library_env_get OPAC_PORT)/"
|
echo " Network: other devices on the LAN can reach the library site at http://${ip}:$(library_env_get STAFF_PORT)/"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
addon_library_menu_builder() {
|
addon_library_menu_builder() {
|
||||||
if library_is_installed; then
|
if library_is_installed; then
|
||||||
load_existing_config
|
load_existing_config
|
||||||
local tab_label="Show the catalogue on the kiosk"
|
local tab_label="Show Find a book on the kiosk"
|
||||||
library_tab_exists && tab_label="Stop showing the catalogue on the kiosk"
|
library_tab_exists && tab_label="Stop showing Find a book on the kiosk"
|
||||||
MENU_LABELS=("Addresses and staff login" "$tab_label" "Network access (this kiosk only / LAN)" "Start" "Stop" "Update" "Uninstall")
|
MENU_LABELS=("Addresses and staff login" "$tab_label" "Network access (this kiosk only / LAN)" "Start" "Stop" "Update" "Uninstall")
|
||||||
MENU_HANDLERS=(action_library_show_access action_library_toggle_tab action_library_network action_library_start action_library_stop action_library_update action_library_uninstall)
|
MENU_HANDLERS=(action_library_show_access action_library_toggle_tab action_library_network action_library_start action_library_stop action_library_update action_library_uninstall)
|
||||||
else
|
else
|
||||||
@@ -111,13 +118,13 @@ library_sync_files() {
|
|||||||
library_add_tab() {
|
library_add_tab() {
|
||||||
load_existing_config
|
load_existing_config
|
||||||
if library_tab_exists; then
|
if library_tab_exists; then
|
||||||
log_info "The catalogue is already one of the kiosk's pages."
|
log_info "Find a book is already one of the kiosk's pages."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
URLS+=("$(library_catalogue_url)")
|
URLS+=("$(library_catalogue_url)")
|
||||||
DURS+=("0")
|
DURS+=("0")
|
||||||
USERS+=("")
|
USERS+=("$(library_env_get VIEWER_USER)") # read-only guest, never the admin login
|
||||||
PASSES+=("")
|
PASSES+=("$(library_env_get VIEWER_PASSWORD)")
|
||||||
NAMES+=("$LIBRARY_CATALOGUE_TAB_NAME")
|
NAMES+=("$LIBRARY_CATALOGUE_TAB_NAME")
|
||||||
save_config
|
save_config
|
||||||
}
|
}
|
||||||
@@ -125,11 +132,12 @@ library_add_tab() {
|
|||||||
# Drop every page that points at the catalogue's local address.
|
# Drop every page that points at the catalogue's local address.
|
||||||
library_remove_tab() {
|
library_remove_tab() {
|
||||||
load_existing_config
|
load_existing_config
|
||||||
local url idx
|
local url old idx
|
||||||
url="$(library_catalogue_url)"
|
url="$(library_catalogue_url)"
|
||||||
|
old="$(library_old_catalogue_url)"
|
||||||
local -a nu=() nd=() nus=() np=() nn=()
|
local -a nu=() nd=() nus=() np=() nn=()
|
||||||
for idx in "${!URLS[@]}"; do
|
for idx in "${!URLS[@]}"; do
|
||||||
[[ "${URLS[$idx]}" == "$url" ]] && continue
|
[[ "${URLS[$idx]}" == "$url" || "${URLS[$idx]}" == "$old" ]] && continue
|
||||||
nu+=("${URLS[$idx]}"); nd+=("${DURS[$idx]:-0}"); nus+=("${USERS[$idx]:-}")
|
nu+=("${URLS[$idx]}"); nd+=("${DURS[$idx]:-0}"); nus+=("${USERS[$idx]:-}")
|
||||||
np+=("${PASSES[$idx]:-}"); nn+=("${NAMES[$idx]:-}")
|
np+=("${PASSES[$idx]:-}"); nn+=("${NAMES[$idx]:-}")
|
||||||
done
|
done
|
||||||
@@ -199,7 +207,7 @@ action_library_install() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ask_yes_no "Show the catalogue on the kiosk screen?" "y"; then
|
if ask_yes_no "Show the Find a book page on the kiosk screen (signed in as the read-only guest)?" "y"; then
|
||||||
library_add_tab
|
library_add_tab
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
@@ -217,10 +225,12 @@ action_library_show_access() {
|
|||||||
ip="localhost"
|
ip="localhost"
|
||||||
echo "(Network access is limited to this kiosk.)"
|
echo "(Network access is limited to this kiosk.)"
|
||||||
fi
|
fi
|
||||||
echo " Catalogue (public search): http://${ip}:$(library_env_get OPAC_PORT)/"
|
echo " Your library (sign in here): http://${ip}:$(library_env_get STAFF_PORT)/"
|
||||||
echo " Staff interface: http://${ip}:$(library_env_get STAFF_PORT)/"
|
echo " Find a book / Add books: http://${ip}:$(library_env_get STAFF_PORT)/add/"
|
||||||
echo " Add books (scan ISBNs): http://${ip}:$(library_env_get QUICKADD_PORT)/"
|
echo " Admin login: ${user} / ${pass}"
|
||||||
echo " Login (staff and add books): ${user} / ${pass}"
|
echo " Read-only login to share: $(library_env_get VIEWER_USER) / $(library_env_get VIEWER_PASSWORD)"
|
||||||
|
echo " Give it out with http://${ip}:$(library_env_get STAFF_PORT)/add/ - it opens Find a book: search only,"
|
||||||
|
echo " it cannot add or change anything (and it has no login on the staff site itself)."
|
||||||
echo
|
echo
|
||||||
echo " Passwords live in $LIBRARY_DIR/.env (root only)."
|
echo " Passwords live in $LIBRARY_DIR/.env (root only)."
|
||||||
pause
|
pause
|
||||||
|
|||||||
+28
-14
@@ -74,6 +74,8 @@ STAFF_PORT=8081
|
|||||||
QUICKADD_PORT=8082
|
QUICKADD_PORT=8082
|
||||||
ADMIN_USER=admin
|
ADMIN_USER=admin
|
||||||
ADMIN_PASSWORD=s3cretPASS
|
ADMIN_PASSWORD=s3cretPASS
|
||||||
|
VIEWER_USER=guest
|
||||||
|
VIEWER_PASSWORD=guestPASS
|
||||||
BIND_ADDR=${BIND_ADDR:-0.0.0.0}
|
BIND_ADDR=${BIND_ADDR:-0.0.0.0}
|
||||||
ENV
|
ENV
|
||||||
echo running > "$STUB_STATE"
|
echo running > "$STUB_STATE"
|
||||||
@@ -98,7 +100,7 @@ source "$REPO/menus/addon_library.sh"
|
|||||||
export KIOSK_UNATTENDED=0
|
export KIOSK_UNATTENDED=0
|
||||||
docker_ensure_installed() { return 0; } # the stub stands in for a working Docker
|
docker_ensure_installed() { return 0; } # the stub stands in for a working Docker
|
||||||
|
|
||||||
tabs() { jq -r '.tabs[] | "\(.url)|\(.duration)|\(.name)"' "$CONFIG_PATH"; }
|
tabs() { jq -r '.tabs[] | "\(.url)|\(.duration)|\(.name)|\(.username)"' "$CONFIG_PATH"; }
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
echo "== lib/docker.sh"
|
echo "== lib/docker.sh"
|
||||||
@@ -133,8 +135,10 @@ printf 'Smith Family Library\nn\ny\n\n' | action_library_install >"$T/install.ou
|
|||||||
check "install.sh got the library name" grep -q 'LIBRARY_NAME=Smith Family Library' "$STUB_LOG"
|
check "install.sh got the library name" grep -q 'LIBRARY_NAME=Smith Family Library' "$STUB_LOG"
|
||||||
check "install.sh got BIND_ADDR=127.0.0.1" grep -q 'BIND_ADDR=127.0.0.1' "$STUB_LOG"
|
check "install.sh got BIND_ADDR=127.0.0.1" grep -q 'BIND_ADDR=127.0.0.1' "$STUB_LOG"
|
||||||
check "is_installed is now true" library_is_installed
|
check "is_installed is now true" library_is_installed
|
||||||
check "catalogue tab added, duration 0" grep -qx 'http://localhost:8080/|0|Library catalogue' <(tabs)
|
check "Find a book tab added, duration 0" grep -qx 'http://localhost:8081/add/#find|0|Find a book|guest' <(tabs)
|
||||||
check "existing tab preserved" grep -qx 'https://example.com|60|Example' <(tabs)
|
check "tab carries the read-only guest password" test "$(jq -r '.tabs[] | select(.name=="Find a book") | .password' "$CONFIG_PATH")" = guestPASS
|
||||||
|
check "admin password never goes in kiosk config" bash -c "! grep -q s3cretPASS '$CONFIG_PATH'"
|
||||||
|
check "existing tab preserved" grep -qx 'https://example.com|60|Example|' <(tabs)
|
||||||
check "untracked config keys preserved" test "$(jq -r .somethingUntracked "$CONFIG_PATH")" = keep-me
|
check "untracked config keys preserved" test "$(jq -r .somethingUntracked "$CONFIG_PATH")" = keep-me
|
||||||
addon_library_status > "$T/status.out"
|
addon_library_status > "$T/status.out"
|
||||||
check "status shows 'this kiosk only'" grep -q 'this kiosk only' "$T/status.out"
|
check "status shows 'this kiosk only'" grep -q 'this kiosk only' "$T/status.out"
|
||||||
@@ -142,21 +146,29 @@ check "status shows 'this kiosk only'" grep -q 'this kiosk only' "$T/sta
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
echo "== menu while installed"
|
echo "== menu while installed"
|
||||||
addon_library_menu_builder
|
addon_library_menu_builder
|
||||||
check "offers 'stop showing' (tab exists)" grep -q 'Stop showing the catalogue' <<< "${MENU_LABELS[*]}"
|
check "offers 'stop showing' (tab exists)" grep -q 'Stop showing Find a book' <<< "${MENU_LABELS[*]}"
|
||||||
check "has Update and Uninstall" grep -q 'Update.*Uninstall' <<< "${MENU_LABELS[*]}"
|
check "has Update and Uninstall" grep -q 'Update.*Uninstall' <<< "${MENU_LABELS[*]}"
|
||||||
|
|
||||||
echo "== tab toggling is idempotent"
|
echo "== tab toggling is idempotent"
|
||||||
library_add_tab >/dev/null 2>&1
|
library_add_tab >/dev/null 2>&1
|
||||||
check "adding again does not duplicate" test "$(tabs | grep -c 'localhost:8080')" = 1
|
check "adding again does not duplicate" test "$(tabs | grep -c 'localhost:8081/add/#find')" = 1
|
||||||
printf '\n' | action_library_toggle_tab >/dev/null 2>&1 # now removes it
|
printf '\n' | action_library_toggle_tab >/dev/null 2>&1 # now removes it
|
||||||
check "toggle removes the catalogue tab" test "$(tabs | grep -c 'localhost:8080')" = 0
|
check "toggle removes the Find a book tab" test "$(tabs | grep -c 'localhost:8081/add/#find')" = 0
|
||||||
check "toggle keeps the other tab" grep -qx 'https://example.com|60|Example' <(tabs)
|
check "toggle keeps the other tab" grep -qx 'https://example.com|60|Example|' <(tabs)
|
||||||
addon_library_menu_builder
|
addon_library_menu_builder
|
||||||
check "menu now offers 'Show'" grep -q 'Show the catalogue on the kiosk' <<< "${MENU_LABELS[*]}"
|
check "menu now offers 'Show'" grep -q 'Show Find a book on the kiosk' <<< "${MENU_LABELS[*]}"
|
||||||
printf '\n' | action_library_toggle_tab >/dev/null 2>&1 # and adds it back
|
printf '\n' | action_library_toggle_tab >/dev/null 2>&1 # and adds it back
|
||||||
check "toggle adds it back" test "$(tabs | grep -c 'localhost:8080')" = 1
|
check "toggle adds it back" test "$(tabs | grep -c 'localhost:8081/add/#find')" = 1
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
echo "== an earlier version's tab (public catalogue address) is cleaned up"
|
||||||
|
jq '.tabs += [{"url":"http://localhost:8080/","duration":0,"username":"","password":"","name":"Library catalogue"}]' "$CONFIG_PATH" > "$T/cfg.tmp" && cp "$T/cfg.tmp" "$CONFIG_PATH"
|
||||||
|
check "old catalogue tab present before" test "$(tabs | grep -c '^http://localhost:8080/|')" = 1
|
||||||
|
library_remove_tab >/dev/null 2>&1
|
||||||
|
check "old catalogue tab removed" test "$(tabs | grep -c '^http://localhost:8080/|')" = 0
|
||||||
|
check "other tabs untouched" grep -qx 'https://example.com|60|Example|' <(tabs)
|
||||||
|
library_add_tab >/dev/null 2>&1
|
||||||
|
|
||||||
echo "== network access"
|
echo "== network access"
|
||||||
: > "$STUB_LOG"
|
: > "$STUB_LOG"
|
||||||
printf 'y\n\n' | action_library_network >/dev/null 2>&1 # allow network
|
printf 'y\n\n' | action_library_network >/dev/null 2>&1 # allow network
|
||||||
@@ -168,8 +180,10 @@ check ".env BIND_ADDR back to 127.0.0.1" test "$(library_env_get BIND_ADDR
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
echo "== access screen"
|
echo "== access screen"
|
||||||
printf '\n' | action_library_show_access > "$T/access.out" 2>&1
|
printf '\n' | action_library_show_access > "$T/access.out" 2>&1
|
||||||
check "shows the staff login" grep -q 'admin / s3cretPASS' "$T/access.out"
|
check "shows the admin login" grep -q 'admin / s3cretPASS' "$T/access.out"
|
||||||
check "shows quick-add address" grep -q 'Add books.*:8082' "$T/access.out"
|
check "shows the read-only login to share" grep -q 'guest / guestPASS' "$T/access.out"
|
||||||
|
check "shows the one library address" grep -q 'sign in here.*:8081/' "$T/access.out"
|
||||||
|
check "shows the add-books path on that address" grep -q ':8081/add/' "$T/access.out"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
echo "== update"
|
echo "== update"
|
||||||
@@ -185,7 +199,7 @@ printf 'n\n\n' | action_library_uninstall >/dev/null 2>&1
|
|||||||
check "compose down without -v" grep -qE 'compose .* down$' "$STUB_LOG"
|
check "compose down without -v" grep -qE 'compose .* down$' "$STUB_LOG"
|
||||||
check "no volume deletion" bash -c "! grep -q -- ' -v' '$STUB_LOG'"
|
check "no volume deletion" bash -c "! grep -q -- ' -v' '$STUB_LOG'"
|
||||||
check "install dir and .env kept" test -f "$LIBRARY_DIR/.env"
|
check "install dir and .env kept" test -f "$LIBRARY_DIR/.env"
|
||||||
check "catalogue tab removed" test "$(tabs | grep -c 'localhost:8080')" = 0
|
check "catalogue tab removed" test "$(tabs | grep -c 'localhost:8081/add/#find')" = 0
|
||||||
|
|
||||||
echo "== purge needs the typed confirmation"
|
echo "== purge needs the typed confirmation"
|
||||||
printf 'y\nnope\n\n' | action_library_uninstall >/dev/null 2>&1
|
printf 'y\nnope\n\n' | action_library_uninstall >/dev/null 2>&1
|
||||||
@@ -195,8 +209,8 @@ library_add_tab >/dev/null 2>&1
|
|||||||
printf 'y\nDELETE\n\n' | action_library_uninstall >/dev/null 2>&1
|
printf 'y\nDELETE\n\n' | action_library_uninstall >/dev/null 2>&1
|
||||||
check "compose down -v --rmi local" grep -q 'down -v --rmi local' "$STUB_LOG"
|
check "compose down -v --rmi local" grep -q 'down -v --rmi local' "$STUB_LOG"
|
||||||
check "install dir removed" test ! -e "$LIBRARY_DIR"
|
check "install dir removed" test ! -e "$LIBRARY_DIR"
|
||||||
check "tab removed again" test "$(tabs | grep -c 'localhost:8080')" = 0
|
check "tab removed again" test "$(tabs | grep -c 'localhost:8081/add/#find')" = 0
|
||||||
check "other tab still there" grep -qx 'https://example.com|60|Example' <(tabs)
|
check "other tab still there" grep -qx 'https://example.com|60|Example|' <(tabs)
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "passed: $PASS failed: $FAIL"
|
echo "passed: $PASS failed: $FAIL"
|
||||||
|
|||||||
Reference in New Issue
Block a user