Commit Graph
456 Commits
Author SHA1 Message Date
Claude 5fc7450af7 library: scan with a phone camera (Quick Add)
A 'Scan with camera' button opens the phone's own camera app; the photo is shrunk
on the phone and read on the server with zbarimg, then goes through the normal add
flow (also available on the sticker step). Works over plain HTTP on iPhone and
Android - no HTTPS needed.

- /api/scan: JPEG/PNG only (checked by magic bytes before ImageMagick sees it),
  12 MB cap, login required, 20 s timeout, temp files cleaned up
- UPC-A reads (zbar reports 13 digits with a leading zero) normalised to the 12
  digits printed on the item so duplicate checks match
- tests/gen_photos.py + tests/check_scan.py: phone-like photos (tilt, blur, noise,
  low light, upside down, EXIF rotation, small, UPC, Code 39 sticker); 10/10 read
  at the size the page sends

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 13:12:58 +00:00
Claude 023a64b62c library: Dockerfile fails loudly and verifies the image
A '|| true' at the end of the install chain swallowed any earlier failure, so a
transient/blocked apt step produced a successful build with no Koha in it (found
when an update rebuilt without the proxy CA). Steps are now separate, memcached and
cron are installed explicitly, and a final step refuses to produce an image that is
missing koha-create, memcached, cron, apache2ctl, zebrasrv, ... Quick Add's image
verifies yaz and python3 the same way.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 11:18:10 +00:00
Claude 49c35f765b library: proxy URL overrides, restore Quick Add header links
- OPAC_URL / STAFF_URL let Quick Add build correct links behind a reverse proxy
- restore the Catalogue/Staff header links (lost when the init block was
  rewritten) and verify them in the browser test

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 11:09:11 +00:00
Claude 76866e8bbc library: barcode modes, patrons/lending, install-time shelves and item types
Quick Add:
- barcode choices: automatic (zero-padded, >= 7 digits), scan a pre-printed
  sticker per item, or none for now; bulk 'give barcodes to items without one';
  reserve a block of numbers and print the sheet ahead of time (link opens
  Koha's range printer prefilled)
- default shelf saved on the server (shared by every device)
- item types follow Koha; CSP allows cover redirects; aria attrs fixed

Koha bootstrap:
- default lending rule (3 weeks, 25 items, 3 renewals, no fines) - a fresh
  Koha refuses every checkout without one
- automatic library-card numbers (start at 1000001), fines off
- Koha's own auto-barcode off (one numbering scheme)
- patron card template (Avery 5371) and layout with a scannable barcode
- LIBRARY_CODE, SHELVES and ITEM_TYPES honoured at first install

Why 7 digits: decoded printed sheets - 7-digit Code 39 read back 40/40, 6-digit
all-numeric symbols were reinterpreted as Code 32 for ~1 in 10 values.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 11:07:02 +00:00
Claude aa7950aa9e library: shelf picker in Quick Add, docs, status/ss fixes; kiosk README entry
- Quick Add: choose the shelf/room (Koha LOC values) once; every scanned item
  gets it and the choice is remembered; invalid values are ignored
- install.sh --status shows localhost in loopback mode; port probing works
  without ss
- library/README.md; kiosk Readme.md add-on section and licenses

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 10:33:58 +00:00
Claude e08133dfc0 library: BIND_ADDR for loopback-only mode, real readiness check
- BIND_ADDR (default 0.0.0.0) on every published port; 127.0.0.1 keeps the
  stack reachable from the host only (Docker bypasses ufw, so this is the
  actual control the kiosk addon's 'this kiosk only' choice uses)
- Koha serves its web-installer page with HTTP 200 before the database
  exists, so 'port answers' was not 'ready': the entrypoint now writes
  /run/koha-ready, the healthcheck requires it, and install.sh waits for
  healthy + the quick-add health endpoint

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 10:30:41 +00:00
Claude 24c95c91f2 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
2026-11-06 10:29:22 +00:00
Claude 2ef8f68b9b library: add Quick Add page (scan an ISBN, the book is added)
A small standard-library Python service next to Koha:
- looks the code up via Z39.50 (yaz) then Open Library over HTTPS
- checks Koha for an existing copy (ISBN-10/13 and UPC/EAN aware)
- adds the record + an item with the next barcode via Koha's REST API
- scanner-friendly UI: auto-focus, audible feedback, duplicate card with
  'add another copy', manual entry when nothing is found, undo, and the
  barcode range to print labels for
Also: enable RESTBasicAuth, map 024$a to the ean column, wire the service,
port and credentials into compose/install.sh.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 10:24:54 +00:00
Claude f4da2608cd library/install.sh: retry builds on Docker Hub rate limits (HTTP 429)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 07:07:56 +00:00
Claude f0a0c4ad40 library: seed label templates/layouts, default item type, fix Apache setup
- Seed Avery 5160/5167 and Demco spine templates plus Spine, Spine+barcode
  and Barcode-only layouts (Dewey call number split across lines)
- Default 942$c (item type) to BOOK so a Z39.50 import saves without edits
- Enable Plack includes in addition to the plain ones (static files/rewrites)
- Swap Apache from mpm_itk to prefork after koha-create (itk can't switch
  users in a container and returned HTTP 500 on every page)
- Drop unneeded SETUID/SETGID caps

Verified live: staff login, Z39.50 search/import against a local server,
save + auto barcode, label PDF with Dewey spine + barcode.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 06:51:50 +00:00
Claude b4c844b124 library: pass PERL5LIB to bootstrap (koha-shell used to set it)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 06:30:12 +00:00
Claude a15478b382 library: run bootstrap via runuser, keep mpm_itk, add SETUID/SETGID caps
- koha-shell -c wraps the command in single quotes, which broke the env
  assignments; use runuser + env instead and verify tables were created
- koha-create requires mpm_itk, so keep it and grant the caps it needs
- stop any Apache started by koha-create before running it in foreground

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 06:29:27 +00:00
Claude 7244cfd918 library: fix entrypoint (Apache user, Listen 8081, start daemons each boot) and crash detection
- Run Apache as the instance user instead of mpm_itk AssignUserID
- Add Listen for the staff port; start zebra/indexer/worker/plack explicitly
- Tolerate koha-plack --enable returning non-zero under set -e
- install.sh: only fail on a crash loop, not a single restart

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 06:20:45 +00:00
Claude 5b79608695 Add library/: one-command Dockerized Koha home library (WIP)
Compose stack (MariaDB + Koha built from the Koha project's Debian
packages), headless bootstrap replacing the web installer, seeded item
types and LoC Z39.50 server, and an install.sh that installs Docker,
generates passwords and waits for the site. End-to-end testing in progress.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
2026-11-06 06:13:25 +00:00
Outis e828805b9c Merge pull request #128 from outis1one/claude/optimistic-clarke-27gpk1
Claude/optimistic clarke 27gpk1
2026-11-05 16:43:42 -05:00
Claude 7cb4a661f9 Add SSH Access: enable sshd and import keys from GitHub/Launchpad (ssh-import-id)
Offered during first-time install and under Addons -> Remote Access.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZQveYdjfcrEsyazq32
2026-11-05 21:41:18 +00:00
Claude eb7bc14a9c Revert Tailscale/Netbird SSH toggles (misread request)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZQveYdjfcrEsyazq32
2026-11-05 21:40:49 +00:00
Claude ead4500406 Add Tailscale SSH and Netbird SSH: first-time install prompt and Remote Access toggles
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZQveYdjfcrEsyazq32
2026-11-05 21:39:11 +00:00
Outis 4085866134 Merge pull request #127 from outis1one/claude/laughing-sagan-2oo2mx
Claude/laughing sagan 2oo2mx
2026-11-02 13:27:52 -05:00
Claude 763162fc57 First boot: detect a stalled online install by silence (10 min), not a 40 min cap
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
2026-10-23 15:37:34 +00:00
Claude d30f7c7260 First boot: don't hang forever on a stalled online install; fall back to the offline bundle
Unattended installs on an --offline ISO cap the online attempt (default 40 min)
and finish from the bundle on timeout or failure. Interactive retry menu gains
[O] to install from the bundle. NodeSource curl gets timeouts.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
2026-10-23 15:33:54 +00:00
Outis d673d04259 Merge pull request #126 from outis1one/claude/sharp-carson-8tbjf5
Netbird menu: enable built-in SSH server on join; document SSH host a…
2026-10-17 13:07:32 -04:00
Claude 6cc5296cd6 Netbird menu: enable built-in SSH server on join; document SSH host aliases
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7JYaTBsSRDoJx2aDEjVp2
2026-10-17 16:57:20 +00:00
Outis 5a4e8a8734 Merge pull request #125 from outis1one/claude/sharp-carson-8tbjf5
Readme: document headless netbird up command
2026-10-17 12:53:33 -04:00
Claude 3e665688c5 Readme: document headless netbird up command
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7JYaTBsSRDoJx2aDEjVp2
2026-10-17 16:50:12 +00:00
Outis 7f8947ade2 Merge pull request #124 from outis1one/claude/laughing-sagan-2oo2mx
Claude/laughing sagan 2oo2mx
2026-10-16 22:40:48 -04:00
Claude d0d39e48bb Region setting (WiFi country, locale, keyboard) on the Super+W screen and Web UI
kiosk-wifi-helper gains region-status/region-set. The country is persisted as
a cfg80211 regdom module option and in the WiFi netplan file; locale and
keyboard go through localectl, validated against what's installed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
2026-10-16 17:48:41 +00:00
Claude 9221fc85aa WiFi: set regulatory domain (country) in the WiFi wizard and web helper
Without a country Linux uses the restrictive world domain: networks show up
in scans but the adapter can't join them. The menu asks (default from the
existing config or system locale); the web helper uses the same default.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
2026-10-16 17:42:44 +00:00
Claude b88d648e14 WiFi: dump link/regdomain/wpa log on failed connect, wait 45s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
2026-10-16 17:32:36 +00:00
Outis 46903b93a4 Merge pull request #123 from outis1one/claude/laughing-sagan-2oo2mx
Claude/laughing sagan 2oo2mx
2026-10-16 12:00:17 -04:00
Claude c196a5f7e7 Turn off WiFi power save and USB autosuspend for Realtek dongles
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
2026-10-16 15:58:43 +00:00
Claude f91dbe4d5f WiFi: adapter chooser, optional wifis, wrong-password diagnosis
- Menu asks which adapter when several exist (shows USB/PCI); the web UI
  helper prefers the adapter already configured.
- Stale-entry cleanup also marks remaining installer wifis optional so a
  dead WiFi entry can't hold up boot.
- After a failed connect, read wpa_supplicant's log to say wrong password /
  network not found / rejected.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
2026-10-16 15:56:46 +00:00
Outis 5e7ffb2eb4 Merge pull request #122 from outis1one/claude/laughing-sagan-2oo2mx
WiFi: drop stale netplan wifis entries before apply
2026-10-16 11:37:12 -04:00
Claude 903a1e45e4 WiFi: drop stale netplan wifis entries before apply
netplan apply waits ~90s for every wifis: interface in every file and then
fails if one doesn't exist (e.g. installer config naming wlan0 while the
adapter is wlp1s0). Remove such entries (backup kept as *.kiosk-bak) before
applying, in both the menu and kiosk-wifi-helper.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
2026-10-16 15:35:06 +00:00
Outis 1987b0ee71 Merge pull request #121 from outis1one/claude/happy-knuth-hwnvbn
Claude/happy knuth hwnvbn
2026-10-10 11:45:37 -04:00
Claude ff962a41c4 WiFi screen: Ctrl+Alt+Super+W, gated by the unlock PIN/password
The WiFi screen now opens with Ctrl+Alt+Super+W, which is easier to
press than the five-key chord. The Shift+W+S+F+H chord and
Ctrl+Alt+Shift+Super+W are removed. Fn isn't part of the combo: most
keyboards never send it to the computer.

With password protection on, the screen asks for the unlock code: the
PIN pad for a PIN, a password field for a password. It shares the
lock screen's wrong-code slowdown. With protection off, it falls back
to the hidden-sites PIN as before. The lock screen's check now goes
through the same checkCode() helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
2026-10-10 15:42:46 +00:00
Claude d6769d5207 Hash the hidden-sites PIN and slow down repeated wrong PINs
.jitsi-pin now holds "sha256:<hex>" (or NOPIN) instead of the digits.
The menu writes the hashed form. A plain-digits file still works and
is converted by the kiosk app at startup.

The PIN is now checked in the main process instead of the PIN pad's
renderer, using the lock screen's slowdown, now factored out
(createCodeThrottle): 5 wrong in a row refuse entry for 30s, doubling
up to 5 minutes. The F10 PIN pad and the WiFi screen share one count.
The WiFi screen's close-after-5-tries is replaced by this. The PIN pad
no longer shows the default PIN on screen, and the menu no longer
echoes the PIN as it's typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
2026-10-10 15:33:05 +00:00
Claude f215c64085 Lock screen: password or 4-8 digit PIN, optional separate boot code
The unlock code and the boot lock code can each be a password or a
4-8 digit PIN. A PIN gets an on-screen number pad, so the lock works on
a touch-only kiosk. The boot lock can use the unlock code or its own.
These are the kiosk's own codes, set in Core Settings -> Password
Protection & Lockout or on the Web UI's Lockout page, never the admin
(sudo) password.

The lock screen moves from an inline data: URL to lockscreen.html. The
code is now hashed and compared in the main process, which also slows
down guessing: after every 5 wrong codes in a row, input is refused for
30s, doubling up to 5 minutes. Existing configs behave as before:
missing fields mean a password, and the same code at boot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
2026-10-10 15:26:59 +00:00
Outis 36f09b2253 Merge pull request #120 from outis1one/claude/happy-knuth-hwnvbn
Claude/happy knuth hwnvbn
2026-10-09 15:53:06 -04:00
Claude 600f5174d8 Add a WiFi screen to the kiosk app (Shift+W+S+F+H)
With the virtual consoles turned off there was no way to join a WiFi
network at the kiosk itself. Holding Shift+W+S+F+H together (or
Ctrl+Alt+Shift+Super+W) now opens a PIN-protected WiFi screen: scan,
pick or type a network, enter the password, connect.

The root side is a small helper (kiosk-wifi-helper: status, scan,
connect only) that the kiosk user may run through a validated
sudoers.d rule. It writes the same netplan file as Core Settings ->
WiFi and puts the previous one back if the new network gives no
address. Installed by provisioning and Advanced -> Upgrade, removed by
Complete Uninstall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
2026-10-09 19:48:09 +00:00
Claude d4f9233655 Add iso/e2b-contig.sh: make an ISO contiguous on a USB drive from Linux
Easy2Boot's UEFI menu returned straight to the menu for the new kiosk ISO
(a 3.5 GB copy on a well-used drive). Some multiboot modes need the ISO in
one unbroken piece, and Linux has no defragmenter for NTFS/FAT32/exFAT -
Easy2Boot's own Linux helpers are a 32-bit udefrag needing i386 libraries
and a FAT32-only perl script.

e2b-contig.sh rewrites just the one file, using only ordinary file
operations - never filesystem structures - so the worst case is "still
not contiguous", never a damaged drive:

1. fill the drive's free space with 4 MB filler files (plugs every gap;
   a plain copy, or fallocate where it exists at all, just fills the
   gaps again - measured on ntfs-3g: 7-8 pieces either way);
2. map the fillers (filefrag -e, which also works on ntfs-3g via FIBMAP)
   and find the longest physically unbroken run of single-piece ones;
3. free just enough of that run and write the copy - the only free space
   left - then delete the fillers;
4. verify the copy's SHA-256 against the original and rename it into
   place (same directory, so the data doesn't move).

If there's no run big enough while the original is on the drive,
--move-out parks it on this computer (checksummed), retries, and puts it
back verified if that still fails. --check only reports. Interrupts and
failures remove fillers and temp copies.

Tested on NTFS images through ntfs-3g: a 60 MB file in 7 pieces became 1
piece with identical content, no leftovers, free space unchanged (27 s for
~900 MB free); drives with no big enough free run (with and without
--move-out) report so and leave the original byte-identical, parked copy
cleaned up. FAT32/exFAT through their FUSE drivers can't report layout,
which the tool detects and stops on, naming the kernel driver to remount
with (vfat/exfat/ntfs3); the kernel drivers aren't available in the test
environment, so those paths are untested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
2026-10-09 18:42:28 +00:00
Outis a66b1e6079 Merge pull request #119 from outis1one/claude/happy-knuth-hwnvbn
Claude/happy knuth hwnvbn
2026-10-09 13:26:37 -04:00
Claude 5cd2117a94 WiFi menu: show its errors, detect adapters by type, keep wired config
Reported: Core Settings -> WiFi "just returns to the core settings menu".
run_menu clears the screen the moment a handler returns, and every early
exit in wifi_menu returned without a pause - so whatever went wrong
(no adapter found, interface wouldn't come up, ...) was wiped before it
could be read.

- wifi_menu now runs the wizard (renamed wifi_configure) and always
  pauses afterwards, success or failure.
- Adapter detection by type (/sys/class/net/*/wireless or phy80211),
  falling back to wl* - not every adapter is named wl*. When none is
  found, lists the interfaces that do exist.
- Clears a software rfkill block via sysfs before `ip link set up`
  (rfkill isn't installed on a server); warns on a hard block; shows
  ip's actual error if bringing the link up still fails.
- Writes its own $NETPLAN_DIR/60-kiosk-wifi.yaml instead of overwriting
  the first *.yaml found - on a fresh install that's the installer's
  50-cloud-init.yaml with the wired config, which it silently dropped.
- SSID/password written as YAML single-quoted scalars: a password with
  " or \ produced invalid YAML before.
- Success = the WiFi interface itself has an IPv4 address (waits up to
  30s), not "the machine has some IP". The SSH revert watchdog uses the
  same test instead of pinging 8.8.8.8, which on a network without
  internet reverted every working connection; with no previous WiFi file
  it reverts by removing ours.

Tested with ip/netplan/sudo stubbed and netplan files in a scratch dir:
installer's wired file untouched, own file written, SSID
`Bob's "Café" #2` and password `p@ss"wo'rd\\:#x` round-trip through a
YAML parser exactly, no-address case reports instead of claiming
success, early errors stay on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
2026-10-09 17:14:01 +00:00
Claude f7901a3684 Don't glob or test inside the kiosk user's private home as the admin
Reported on a 26.04 offline install, first-boot step 4:
  chown: cannot dereference '/home/kiosk/kiosk-app/*.js': No such file or directory
(same for *.mjs, *.html). `sudo chown ... "$KIOSK_DIR"/*.js` has the glob
expanded by the calling (admin) shell before sudo runs; the kiosk user's
home is created mode 750, so the admin can't list it, the pattern stays
literal, and chown fails - stopping the install under set -e.

- provision_install_app: chown the copied top-level files with
  `sudo find "$KIOSK_DIR" -maxdepth 1 -type f -exec chown ...` (root does
  the listing). Top level only, so chrome-sandbox inside node_modules
  keeps its root ownership.
- Swept lib/ and menus/ for the same class of bug: Asterisk Intercom's
  baresip_is_installed and status read $KIOSK_HOME/.baresip/accounts
  without sudo, so they always reported "not configured". Now sudo test
  / sudo head. Everything else that touches the kiosk home (config.json,
  kiosk-app checks) already goes through sudo -u kiosk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
2026-10-09 16:59:28 +00:00
Claude b0c0008d44 Create missing groups before adding the kiosk user (no netdev on 26.04)
Reported on a 26.04 offline install, first-boot step 2:
  useradd: group 'netdev' does not exist
useradd refuses to create the user at all when any -G group is missing,
which stopped provisioning. Ubuntu 26.04 Server's base system has audio,
video, input and plugdev but no netdev (checked against the 26.04.1
ISO's /etc/group).

provision_create_kiosk_user now `groupadd -f -r`s each supplementary
group first (a no-op when it exists), then passes the same list to
useradd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
2026-10-09 16:55:11 +00:00
Outis 50cf0bef1d Merge pull request #118 from outis1one/claude/happy-knuth-hwnvbn
Fix first-boot service never starting on Ubuntu 26.04
2026-10-09 11:57:37 -04:00
Claude 1ba9e0a8aa Install WiFi tools (wpasupplicant, iw) in the base kiosk install
A default Ubuntu Server 26.04 install only gets wpasupplicant/iw when
WiFi is configured in the installer's network screen (they're not in the
server manifest otherwise). Without them netplan can't join a WiFi
network and Core Settings -> WiFi stops at "No WiFi tools found" - which
is exactly the offline-first case: install with no network, then use the
kiosk's WiFi menu to connect.

- Add wpasupplicant and iw to PROVISION_APT_PACKAGES, so first boot
  installs them in step 1 (from the offline bundle when there's no
  internet) - before Core Settings -> WiFi is offered. +1.6 MB in the
  bundle.
- wifi_menu installs them itself (via run_with_offline_fallback) if
  they're missing, instead of erroring with an apt command to run.

Bundle rebuilt: contains wpasupplicant and iw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
2026-10-09 15:54:59 +00:00
Outis c15ff2f098 Merge pull request #117 from outis1one/claude/happy-knuth-hwnvbn
Fix first-boot service never starting on Ubuntu 26.04
2026-10-09 11:42:08 -04:00
Claude b479b68219 Fix first-boot service never starting on Ubuntu 26.04
Reported: installed from the ISO, files present, but the kiosk setup
never appeared on first boot.

Cause: kiosk-firstboot.service had After=cloud-final.service, and on 26.04
cloud-final.service is After=multi-user.target - while multi-user.target,
which wants kiosk-firstboot, is implicitly After= it. An ordering cycle;
systemd breaks it at boot by deleting a job, and kiosk-firstboot's was
the one to go. systemd-analyze verify against the 26.04.1 ISO's own units
reproduces it:

  Found ordering cycle: kiosk-firstboot.service/start after
  cloud-final.service/start after multi-user.target/start - after
  kiosk-firstboot.service

- Drop After=cloud-final.service. The script's `cloud-init status --wait`
  goes too: cloud-final waits for multi-user.target, which waits for this
  service - it would deadlock. DPkg::Lock::Timeout already covers what it
  was guarding against (dpkg lock contention).
- Drop Conflicts=getty@tty1.service: getty.target pulls getty@tty1 into
  the same boot transaction, and a conflict between two wanted jobs is
  also resolved by dropping one. Before=getty@tty1 alone keeps tty1's
  login prompt waiting until this oneshot finishes.

Verified: systemd-analyze verify of kiosk-firstboot + multi-user.target +
cloud-final + cloud-init.target + getty@tty1 + graphical.target in the
26.04.1 server filesystem - old unit reports the cycle, fixed unit is
clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
2026-10-09 15:39:24 +00:00
Outis b660ba89db Merge pull request #116 from outis1one/claude/happy-knuth-hwnvbn
Claude/happy knuth hwnvbn
2026-10-03 12:51:35 -04:00