Commit Graph
2234 Commits
Author SHA1 Message Date
maziggy a4c7d6d5cf Updated issue template 2026-04-20 15:10:59 +02:00
maziggy 1de4e409b0 fix(printer): suppress "not homed" re-prompt after Auto Home (#1052 follow-up)
The bed-jog "not homed" warning modal was gated on a session-scoped
  "warned" flag set only by the "Move anyway" button. Clicking "Auto
  Home" sent the G28 and closed the modal but never set the flag, so the
  next jog click in the same session re-prompted — even though the
  printer was now homed.

  The homeAxes mutation's onSuccess handler now flips the same
  `bambuddy.bedJog.warned.<printerId>` sessionStorage flag. The warning
  still fires once per printer per session (intended safety guard,
  cleared on restart), but not repeatedly after a successful auto-home.
2026-04-20 13:06:29 +02:00
maziggy 7026a6de77 fix(printer): bed-jog "Home Z" could crash bed into toolhead on H2C/H2D/H2S/X1 (#1052)
Critical safety fix. The bed-jog dialog's "Home Z" button sent a bare
  `G28 Z` over gcode_line. On Bambu printers where the Z endstop is at
  the top (bed moves UP into it — H2C, H2D, H2S, X1 family), `G28 Z`
  skips the toolhead-park step that a full `G28` runs first, so the bed
  rises at full speed with nothing getting out of the way. The reporter
  only escaped damage because the toolhead happened to be parked on the
  purge chute.

  The /printers/{id}/home-axes endpoint and BambuClient.home_axes() now
  always send bare `G28` regardless of the axes argument, triggering the
  firmware's safe multi-step routine (park toolhead → home XY → home Z).
  The axes argument is kept for API compat but ignored; invalid values
  still return 400.

  Frontend retitles the button "Auto Home" and updates the dialog copy
  in all 7 locales so users aren't surprised when X/Y motion happens
  before Z. Parameterized regression test asserts z/xy/all all produce
  bare G28.
2026-04-20 12:56:31 +02:00
maziggy bc895dff6a ● fix(ams): restore Configure/Assign actions on reset slots + relax Assign Spool filtering (#1047)
Three related fixes reported together:

  (1) After resetting an AMS slot, the printer card showed "Empty Slot"
  with no Configure or Assign Spool actions while SpoolBuddy's AMS page
  still let the user re-configure the same slot. Commit c9efa4b8 (#784)
  added a `tray?.state === 10` gate to the EmptySlotHoverCard actions,
  intended to hide them on physically-empty slots (state=9). In practice
  firmware often reports state=9 (or omits state entirely) after a
  user-initiated reset even when a spool is still present, so the gate
  hit the wrong case. The gate was redundant anyway — EmptySlotHoverCard
  only renders when tray_type is empty — so it's removed at both the
  standard-AMS and AMS-HT render paths.

  (2) After configuring a slot with a Generic profile, the Assign Spool
  modal hid manually-added inventory spools even when material matched,
  unless the user flipped "Show all spools". The filter required exact
  slicer_filament_name equality, which manually-added spools don't
  populate. Filter now prefers exact slicer-profile match when both
  sides have one, and falls back to partial material match in either
  direction (so a "PLA" spool shows up for a "PLA Basic" slot).

  (3) On assign, the mismatch dialog fired on every Generic spool
  because Bambu Studio / OrcaSlicer profile names carry an @printer
  nozzle (variant) qualifier while the tray stores the bare base name.
  Both the filter and checkProfileMatch now strip everything from @
  onward before comparing.

  Adds 3 regression tests covering each path.
2026-04-20 12:45:49 +02:00
maziggy 1c076850ad Updated CHANGELOG 2026-04-20 11:56:33 +02:00
maziggy c894899baf ● chore(i18n): collapse informational drift to summary counts
The parity gate expansion in 8f9eb0d4 started printing the full
  missing-key and placeholder-mismatch lists for every informational
  locale on every test run, which was noisy given CI only cares about
  strict locales. Collapse info reports to one line per category
  (`fr: missing keys vs en: 74`) and keep the full lists available via
  VERBOSE_INFO=1 for when someone is actually catching up a locale.
2026-04-20 11:55:18 +02:00
maziggy e5dfb96351 fix(skip-objects): enlarged plate preview fails to load on auth-enabled instances (#1046)
The mini thumbnail wrapped its src with withStreamToken() (appends the
  short-lived camera-stream token, needed because <img> can't send an
  Authorization header), but the enlarged lightbox <img> used a bare
  ${status.cover_url}?view=top. On auth-enabled instances the backend
  rejected the unauthenticated request and the browser showed the
  broken-image icon. Wrap the enlarged src with withStreamToken() too.
2026-04-20 11:49:45 +02:00
maziggy d0f35e5d60 fix(mqtt): cap task_id at int32 max to prevent P1S dispatch stalls (#1042) 2026-04-20 08:46:57 +02:00
maziggy d3425c7f44 fix(ftp): wait for zombie thread to complete before giving up on download (#1014) 2026-04-20 08:39:09 +02:00
maziggy ea78fe720c fix(obico): clear Status banner on next successful detection cycle (#172) 2026-04-20 08:19:55 +02:00
maziggy 5a28964748 Change the color catalog's default manufacturer filter from "Bambu Lab" to "All Manufacturers" (#1039) 2026-04-20 08:15:11 +02:00
maziggy 66fe4860f8 fix(printers): stop controls row overflowing in Chrome at narrow card widths 2026-04-19 15:12:07 +02:00
maziggy 685c8e5f2c Post work PR #939 2026-04-19 14:58:33 +02:00
Ed b046c2cac4 Enhance plate-clear tracking and visibility in printer cards (#939)
* implement plate clear button, add plate status indicator, enable hide on setting change

* added plate cleared icon

* added smaller plate cleared button on "small" printers view

* tighten layout slightly

* fix(printers): restore plate-clear card controls
2026-04-19 14:54:03 +02:00
maziggy 74527d4124 fix(smart-plug): restore MQTT subscriptions for per-type topic configs on startup (#1010)
Users integrating a Shelly plug through an external MQTT broker
  (ioBroker, Zigbee2MQTT, HA's MQTT broker, etc.) lost the plug's
  power/state/energy readings after every Bambuddy restart. The only
  fix was opening Settings → Smart Plugs, renaming the topic to a dummy
  value, saving, renaming back, and saving again.

  Root cause: three code paths configure an MQTT smart plug's
  subscriptions — the startup restore in main.py, the create route,
  and the update route — and they had drifted. The create/update
  routes used the newer per-type model (mqtt_power_topic /
  mqtt_energy_topic / mqtt_state_topic with per-type paths,
  multipliers and mqtt_state_on_value) while the startup restore was
  still on the legacy single-topic model. Worse, the restore loop
  short-circuited on `if plug.mqtt_topic:`, skipping any plug whose
  topics were only set in the new per-type fields — exactly the shape
  of a Shelly-via-ioBroker config, which publishes power and state on
  separate topics. The "rename, save, rename back" workaround routed
  through the update endpoint and re-established the subscription the
  correct way.

  Extracted the topic-resolution + service.subscribe() call into
  subscribe_plug_to_mqtt() in mqtt_smart_plug.py and routed all three
  paths through it so the schema can't drift again. The helper keeps
  the legacy `mqtt_topic` field working as a fallback for all three
  data types — matching the behaviour the startup restore used to
  have via subscribe()'s internal `effective_*_topic or topic`
  collapsing, and matching the change-detection dict already used
  during updates.

  Regression tests cover: per-type topics restored without a legacy
  topic, legacy single-topic backward compat, per-type multipliers
  overriding legacy, per-type winning when both are set, the
  empty-config skip case, and topic-list de-duplication.
2026-04-19 13:51:58 +02:00
maziggy 936b748127 fix(archive): truncation of large 3MF uploads on sendfile short-return (#1032)
On bare-metal Raspberry Pi OS bookworm / armv7l / Python 3.11, 3MF
  files larger than a few megabytes arrived complete via the
  virtual-printer FTP server but the copy into data/archives/ was
  silently truncated. The archive row was still written, the printer
  card looked fine, and the problem only surfaced later when opening
  the archive — the subsequent zipfile.ZipFile() in
  GET /archives/{id}/plates raised BadZipFile and the UI came up blank
  with no thumbnail, plate list, or filament data.

  Two things conspired:

  1. archive_print() used shutil.copy2, which takes Python's sendfile()
     fast path on Linux. On the reporter's kernel/fs combination
     sendfile returned a short count on the first call for the upload
     sizes hit in practice and the destination ended up truncated.
     Small files completed in one syscall and were fine.
  2. ThreeMFParser.parse() caught the resulting BadZipFile in a bare
     `except Exception: pass`, so the archive pipeline kept going with
     empty metadata and left the bad file on disk — nothing in the
     logs hinted anything had gone wrong until a support bundle came
     in and the "Failed to parse plates" warning fired much later.

  The archive copy is now an explicit chunked read/write with fsync —
  sendfile is not in the path. After the copy, if the source was a
  valid ZIP but the destination isn't, we refuse to create the archive
  row, remove only the truncated file (and the archive directory if
  empty — archive_dir is created with exist_ok=True so rmtree would be
  unsafe if a same-second same-filename collision happened), and log
  both sizes at ERROR so the condition is obvious in future support
  bundles. The parser's silent catch now logs at WARNING for the same
  reason.

  All nine archive_print() call sites already check `if archive:` or
  `if not archive:`, so returning None for corrupted ZIPs propagates
  cleanly without behaviour changes elsewhere.

  Regression tests cover single-chunk and multi-chunk copies, mtime
  preservation via copystat, overwrite of an existing destination, a
  ZIP roundtrip through a multi-megabyte 3MF, the new parser WARNING,
  and a truncation sentinel verifying that zipfile.is_zipfile() flips
  to False on a half-written ZIP — the exact post-condition
  archive_print now trusts.
2026-04-19 13:40:43 +02:00
maziggy 32c0b169bd fix(frontend): thumbnails blank until reload after sign-in
On auth-enabled instances, logging out and back in left the File Manager
  (and occasionally the Archives page) full of broken thumbnails until a
  manual page reload. Thumbnail URLs are gated by a short-lived camera
  stream token that <img> tags cannot send via Authorization headers, so
  the token is appended as ?token=… at render time.

  Two races broke this after sign-in:

  1. The token query was keyed on ['camera-stream-token'] alone and fired
     while the user was still on the login page. It 401'd, React Query
     cached the failure with a 50-minute staleTime, and nothing invalidated
     it after login — the token never arrived.

  2. Even when the token did arrive, the module-level variable holding it
     was not reactive, so pages that had already rendered kept serving
     image URLs with no token in them.

  Fixes:

  - Include user.id in the query key and gate with
    `enabled: authEnabled ? !!user : true`. A new sign-in produces a new
    key and triggers a fresh fetch; no anonymous fetch is cached.
  - When the token transitions from null to a value, walk the DOM once
    and update src on every <img>/<video> pointing at /api/v1/ without
    the current token so already-rendered pages reload in place.
  - Mirror the query key/gate in CameraPage so it shares the cache entry.

  The DOM-rewrite logic is extracted into rewriteMediaSrcWithToken() with
  unit tests covering: appending to a query-less URL, & separator with an
  existing query, skipping URLs that already carry the current token,
  replacing a stale token (trailing and middle positions), leaving
  non-/api/v1/ URLs alone, updating <video>, and URL-encoding tokens with
  special characters.
2026-04-19 13:27:41 +02:00
maziggy c7ad449e4e fix(firmware): parse P2S/X2D wiki anchors without dash and full-width parens (#1030)
The wiki scraper silently returned no versions for P2S and X2D, causing
  Bambuddy to fall back to the Bambu Lab download page, which still listed
  01.01.01.00 as "latest" even though 01.02.00.00 shipped on 2026-04-09.

  Two regex mismatches in _fetch_all_versions_from_wiki():

  1. Heading anchor ids require an optional dash between version bytes and
     date. H2D/X1/H2C/H2S use "h-01020000-20260409"; P2S and X2D publish
     "h-0102000020260409" (no dash).
  2. The text fallback only matched ASCII parens around release dates, but
     P2S, X2D, A1 and A1-mini render dates in full-width parens (YYYYMMDD)
     (U+FF08/U+FF09).

  Anchor regex now accepts an optional dash; fallback accepts both paren
  styles. Added regression tests for both shapes.
2026-04-19 12:27:06 +02:00
maziggy 2bf397e33e fix(queue): update LibraryFile.print_count and last_printed_at on completion (#1008)
Both fields have existed on the model and been shown in the File
  Manager for some time, but nothing ever wrote to them — every file in
  every library appeared to have never been printed.

  Now on_print_complete's queue-status update path calls a small
  _bump_library_file_usage_if_completed() helper that increments
  print_count and stamps last_printed_at on the source library file
  whenever a queued print completes successfully. Failed, cancelled and
  user-aborted prints are intentionally skipped so the fields represent
  successful usage rather than attempt count.

  Unblocks sorting the File Manager by last-printed date and is a
  prerequisite for the scheduled-purge feature requested in #1008,
  which is held until we see whether manual sort+bulk-delete covers the
  use case.
2026-04-19 12:15:25 +02:00
maziggy 578aa75eee chore(security): suppress three Debian-postponed CVEs in Trivy scans
Add CVE-2026-6385, CVE-2026-30997 and CVE-2026-6192 to .trivyignore.
  All three are marked "vulnerable / postponed" in both bookworm and
  trixie by the Debian Security Tracker with no upstream fix yet, so
  the Trivy container scan will keep re-raising them on every run.

  None of the vulnerable code paths are reachable in Bambuddy:

    * CVE-2026-6385 (ffmpeg DVD subtitle heap OOB write) — ffmpeg here
      only ingests printer-camera RTSP and MJPEG/H.264/H.265 streams,
      never DVD/VOB files with subtitle tracks.
    * CVE-2026-30997 (ffmpeg AV1 decoder OOB read → DoS) — Bambu
      printer cameras emit H.264/H.265/MJPEG, not AV1.
    * CVE-2026-6192 (openjpeg JPEG 2000 integer overflow) —
      libopenjp2-7 is pulled in transitively by ffmpeg but Bambuddy
      never decodes JPEG 2000 files.

  Not caused by the recent bookworm → trixie runtime image switch;
  both releases carry the same "postponed" status. Rationale captured
  inline next to each CVE for future auditors.
2026-04-19 11:51:51 +02:00
maziggy 5e5e8a519d feat(file-manager): collapse folders by default toggle (#996)
Add a "Collapse" toggle in the File Manager sidebar header next to
  "Wrap". When enabled, the folder tree opens with only top-level
  folders visible on every page load; disabled restores the previous
  fully-expanded default. Toggling the preference also immediately
  re-collapses or re-expands the current tree via a key-remount trick
  on each top-level FolderTreeItem, so the change takes effect without
  a page reload. Preference persists to localStorage under
  library-collapse-folders, matching the existing library-* convention.

  Backwards-compatible: FolderTreeItem gains an optional
  defaultExpanded prop defaulting to true, so no callers see a
  behavior change. Missing localStorage key coerces to false, so
  existing users keep the old expanded-by-default behavior until they
  flip the toggle.

  New strings added to all 8 locales under fileManager.*. Wiki
  "File Manager" page gains a "Folder sidebar preferences" section
  that documents both Wrap and Collapse toggles. Four vitest cases
  cover default, preloaded-collapsed, click-to-collapse, and
  click-to-expand paths.
2026-04-19 11:47:46 +02:00
maziggy b655b1211e chore(docker): switch runtime image to Debian Trixie
Picks up ffmpeg 5 → 7 (HEVC/AV1 improvements), OpenSSL 3.0 → 3.3, and
  two more years of APT package freshness. Frontend-builder stays on
  Bookworm until the Node.js image team publishes Trixie variants.
2026-04-19 10:57:07 +02:00
maziggy d17c87c982 Bumped version 2026-04-19 10:07:44 +02:00
maziggy e7672e34ac chore(ci): silence false-positive security findings
- Bandit B108: mark 3 dummy /tmp paths in test fixtures as nosec
  - CodeQL py/ldap-injection: already RFC 4515 escaped via _ldap_escape()
  - CodeQL py/incomplete-url-substring-sanitization: test-only assertions
  - GitGuardian: replace sample passwords with <placeholder> strings in
    notification-template preview data
2026-04-19 10:02:31 +02:00
maziggy 56b83ca020 chore(ci): silence false-positive Bandit B108 + CodeQL LDAP/URL findings 2026-04-19 09:59:09 +02:00
maziggy 10c261dcf2 chore(tests): suppress B108 on dummy /tmp test fixtures 2026-04-19 09:48:10 +02:00
maziggy 61e40f0d09 Merge origin/main — commits already present via dev 2026-04-19 09:44:36 +02:00
maziggy ed17728cef Added UPDATING.md 2026-04-19 09:42:25 +02:00
maziggy 71afe35a49 Added new update.sh and update docs 2026-04-19 09:08:30 +02:00
maziggy 8f9eb0d433 chore(i18n): extend parity gate to all locales with strict/info tiers
Previously the script only inspected en/zh-CN/zh-TW, leaving de/fr/it/ja/pt-BR
  drift invisible. Now locales are auto-discovered from src/i18n/locales/, and a
  STRICT list (de, zh-CN, zh-TW — currently in parity) gates CI while the rest
  report informationally until their drift is caught up. ja notably has 27 real
  placeholder bugs worth fixing before promotion to strict.
2026-04-19 08:36:13 +02:00
maziggy 946ebb6307 Bumped version 2026-04-19 08:28:25 +02:00
maziggy 8af2492543 Post work PR #1025 2026-04-19 08:24:23 +02:00
Minidoracat a584e671ec feat(i18n): add zh-TW locale and sync 74 missing keys in zh-CN (#1017) (#1025)
* fix(i18n): sync zh-CN to match en structure

- Add 74 missing keys covering login.resetPassword, printers.firmwareModal
  badges, settings.spoolbuddy device management, settings.tabs.spoolbuddy,
  spoolbuddy.settings system config
- Fix fileManager.uploadFailed placeholder bug (had stray {{count}} copied
  from zipFilesFailed; en value is plain "Upload failed")

Refs #1017

* feat(i18n): add zh-TW locale and enforce 3-way parity

- Add frontend/src/i18n/locales/zh-TW.ts (Traditional Chinese, Taiwan usage)
  with full key set aligned to en.ts
- Register zh-TW in frontend/src/i18n/index.ts: import, resources,
  supportedLngs, availableLanguages
- Add frontend/scripts/check-i18n-parity.mjs: TypeScript Compiler API-based
  3-way gate checking key set equality, placeholder equality, and legacy
  _plural / _one+_other suffix handling across en / zh-CN / zh-TW
- Wire check:i18n into test:run npm script so frontend-tests CI job
  (ci.yml:227) gates future locale drift

Fixes #1017
2026-04-19 08:17:09 +02:00
maziggy bb999c6805 Post work PR #1024 2026-04-19 08:13:17 +02:00
Sn0rrii e958b10f75 fix(oidc): raise callback code/state max_length from 512 to 2048 (#1024)
Facebook and some other OAuth providers issue authorization codes that
exceed 512 characters. Pydantic rejected these with 422 string_too_long.
The OAuth spec defines no maximum code length; 2048 aligns with common
provider limits.

Also adds three integration tests to verify 512-char and 2048-char codes
are accepted while 2049-char codes are correctly rejected.
2026-04-19 08:10:56 +02:00
maziggy 68920f8c62 Fix virtual printer dropping null-terminated MQTT payloads from OrcaSlicer Linux (#927)
OrcaSlicer's Linux BBLNetworkPlugin publishes MQTT payloads with the
  C-string null terminator included in the length, so decoded messages
  arrived as `{…}\x00`. The strict json.loads() raised JSONDecodeError
  and the publish handler silently returned — pushall, get_version, and
  project_file were never answered, and the slicer hit its 60 s sync
  timeout. Print_queue mode only (proxy mode tunnels MQTT). The b069b521
  serial-adaptation fix was correct but ran past this earlier silent
  failure.

  _handle_publish now strips trailing \x00/whitespace before parsing and
  logs the raw payload on any remaining decode failure so future silent
  variants are visible in support bundles.
2026-04-19 08:04:05 +02:00
maziggy 2366a1a0b3 Fixed backup fie name 2026-04-18 19:04:31 +02:00
maziggy 8c4253c5f1 Updated .gitignore 2026-04-18 14:54:01 +02:00
MartinNYHC e21af6d2bc Fix Discord link in README.md 2026-04-18 14:25:40 +02:00
MartinNYHC 86a640f072 Fix duplicate forum link in README
Removed duplicate forum link and adjusted formatting.
2026-04-18 14:24:47 +02:00
MartinNYHC 566f6cc680 Update Discord link in README 2026-04-18 14:23:30 +02:00
maziggy d2ef8834a9 Revert "Updated README"
This reverts commit 9b7a13b4ad.
2026-04-18 14:22:52 +02:00
maziggy 9b7a13b4ad Updated README 2026-04-18 14:21:51 +02:00
maziggy 6cb3457102 Updated README 2026-04-18 14:20:45 +02:00
maziggy b92d4a7445 Post work PR #1013 2026-04-18 12:39:07 +02:00
Minidoracat baf0716a9a feat(cloud): support China region for token-based login (#1013)
feat(cloud): support China region for token-based login

The /cloud/token endpoint always used the global Bambu API endpoint,
so users with China-region access tokens could not validate their
token. The password login flow already exposes a region selector; this
brings the token flow to parity.
2026-04-18 12:30:01 +02:00
maziggy 115d6fe627 fix(mqtt): unique per-submission IDs for archive reprints (#1011)
Archive reprints and library-file prints built the MQTT project_file
  command with hardcoded project_id="0", subtask_id="0", task_id="0".
  Printers key per-job state (including gcode_start_time) on those IDs,
  so reprints looked like continuations of the same job and third-party
  MQTT observers (OctoEverywhere) reported compounding durations across
  repeat replays — a 40 min job reprinted from archive showed ~1h40m,
  and a second reprint of the same file showed ~4h. BambuStudio mints
  fresh IDs per submission; bambu_mqtt.start_print() now does the same
  using an epoch-millisecond timestamp for all three fields. md5 is
  deliberately left empty to avoid activating firmware md5-validation
  against a digest we can't compute without re-reading the upload.

  Added 6 regression tests in TestStartPrintUniqueIdentityFields
  covering non-zero IDs, md5 stays empty, uniqueness across successive
  submissions, numeric-string format, and blast-radius guard on
  unrelated payload fields. Updated CHANGELOG.
2026-04-18 09:09:21 +02:00
maziggy a2c7fd4542 fix(obico): revert POST-bytes approach — Obico /p/ is GET-only
The 0.2.3b4 #1003 "fix" POSTed JPEG bytes as multipart form data,
  but Obico's /p/ endpoint is declared methods=['GET'] upstream and
  reads ?img=URL from the query string. Every POST was 405'd by
  Flask's router before any handler ran, which is why the Obico
  container logs were silent while Bambuddy kept reporting
  "ML API call failed for printer N:" with a blank suffix —
  raise_for_status() on the 405 produced an exception whose str()
  rendered empty.

  Restored the pre-#1003 nonce-URL approach (commit 3e434458):
  capture locally with a 20s timeout we control, stash the JPEG
  under a single-use 32-byte nonce, hand Obico a
  GET /api/v1/obico/cached-frame/{nonce} URL that resolves in
  <50ms so its hardcoded 5s read timeout never races RTSP.

  Also guards against future silent exceptions: the error format
  now falls back to type(exc).__name__ when str(exc) is empty.
  Detection also early-returns with an explicit error if
  external_url is unset instead of handing Obico a URL it can't
  resolve.

  The #1003 reverse-proxy scenario (Authelia/Authentik/CF Access
  in front of Bambuddy) is addressed by documenting that the
  /api/v1/obico/cached-frame/ path must be whitelisted from
  external auth at the proxy layer — it is already public on
  Bambuddy's side.

  Backend: services/obico_detection.py, api/routes/obico.py,
  main.py (PUBLIC_API_PATTERNS).
  Frontend: FailureDetectionSettings banner + client.ts type +
  all 7 locales restored.
  Tests: 15 unit + 5 integration tests pass.
2026-04-18 08:50:46 +02:00
maziggy 464d56ea0d fix(install): make SpoolBuddy kiosk usable on first boot in full-mode install
Full-mode install booted into an unusable kiosk:
  - Chromium opened before uvicorn → "can't connect to localhost"
  - After reload, requires_setup=true hijacked /spoolbuddy → /setup
  - Touch-only Pi has no keyboard to complete the setup wizard
  - Declining auth left the user at / instead of the kiosk

  Fixes, bundled:

  1. backend/app/cli.py kiosk-bootstrap now, in one DB transaction:
     - creates a scoped API key (can_read_status=True, rest false)
     - upserts setup_completed=true
     so AuthContext never redirects and the kiosk URL loads directly. Users
     who want auth can still enable it from the admin UI; the provisioned
     key keeps working.

  2. install.sh full-mode runs the CLI as the bambuddy service user after
     create_bambuddy_service and sed-replaces the CHANGE_ME_AFTER_SETUP
     placeholder in spoolbuddy/.env.

  3. The generated spoolbuddy-kiosk-launch polls ${backend_url}/health for
     up to 60s before exec'ing chromium, so cold boots wait for uvicorn
     instead of flashing ERR_CONNECTION_REFUSED.

  Standalone mode was unaffected — users supply a real key from their
  existing Bambuddy before install.
2026-04-18 08:14:53 +02:00
maziggy 3502ab33c5 fix(install): auto-provision SpoolBuddy kiosk API key in full-mode install
Full-mode install wrote CHANGE_ME_AFTER_SETUP as SPOOLBUDDY_API_KEY because
  no admin exists yet to create a real one. On reboot the kiosk launched with
  that placeholder, AuthContext rejected it, and the user hit the Bambuddy
  login page instead of the kiosk. Standalone mode was unaffected — users
  paste a real key from their existing Bambuddy before install.

  Adds backend/app/cli.py with a kiosk-bootstrap subcommand that creates a
  scoped APIKey row directly in the DB (can_read_status=True, everything else
  false) and prints the full key to stdout. install.sh full-mode runs it as
  the bambuddy service user after create_bambuddy_service, captures the key,
  and sed-replaces the placeholder in spoolbuddy/.env. Idempotent with
  --force for re-installs.

  Drops the outdated "create an API key and edit .env" next-step block since
  the kiosk is now provisioned automatically.
2026-04-18 07:40:38 +02:00