turulix's headless slicing pipeline got cloud preset IDs from
/api/v1/cloud/settings (the /cloud/* gate from #1182 worked), but
slicing those IDs via POST /library/files/{id}/slice failed with
"no Bambu Cloud session is stored" — the slice route lives on a
different router, never saw the api_key_owner stash, and
_resolve_cloud fell through to the empty auth-disabled global
Settings token.
Add a permissive route-level dep that returns the API key's owner
when the key has the cloud scope and None otherwise (never raises),
so non-/cloud/* routes can opt in without breaking the local-preset
path. Wire it into POST /library/files/{id}/slice and
GET /slicer/presets (same root cause, would hit any UI proxied
through an API key). The route picks current_user or
api_key_cloud_owner before deriving user_id.
Auth gate's None-return for API keys is unchanged — keeping the
owner-resolution scoped to the routes that actually need a cloud
token prevents scope creep into routes that fence on
``current_user is None``.
Tim (@turulix) is building a fully automated headless slicing pipeline
against Bambuddy's API and hit the wall flagged in #665: /cloud/* routes
resolve cloud_token per-user from User.cloud_token, but the auth gate
returned None for API-keyed requests, so the route fell back to the
global Settings-table token, which only carries a value in auth-disabled
deployments. Net effect on auth-enabled deployments: API keys reached
the gate just fine, then /cloud/filaments always saw user=None and
returned 401 / empty results — no path to read slicer presets or the
filament catalogue that a CLI workflow needs.
Make API keys carry an owner and route /cloud/* lookups through that
owner; gate the new capability behind an explicit opt-in scope so
existing automation doesn't gain cloud-read access on upgrade.
- APIKey gains user_id (FK to users.id, ON DELETE CASCADE) and
can_access_cloud (BOOLEAN DEFAULT 0). User-delete route also runs an
explicit DELETE FROM api_keys WHERE user_id = ? since SQLite ships
FK enforcement off — same pattern as the existing created_by_id
cleanup blocks.
- New cloud_caller dep on /cloud/* routes resolves to the JWT user OR
the API-key owner stashed by a router-level gate. The auth gate itself
continues to return None for API keys so #1182's surface stays bounded
to /cloud/* — without that bound, any route that fences API keys via
`if current_user is None: raise 403` (e.g. long-lived-token
management) would silently start accepting them.
- The /cloud/* router-level dep enforces three independent fences for
API-keyed callers: user_id IS NOT NULL (legacy keys → 401 with
recreate copy), can_access_cloud=True (otherwise 403), and owner has
cloud_token (existing fence, unchanged). Two extra one-shot fence
errors at create/update time refuse can_access_cloud=True when auth
is disabled or the key is ownerless.
- Frontend: APIKey list shows "Cloud" badge on cloud-enabled keys and
"Legacy" badge on ownerless rows; create form gains an "Allow cloud
access" toggle, default off. New i18n keys in all 8 locales (en + de
fully translated, others seeded with English fallbacks pending native
translation — matches the project's flow for newly-added features).
Migration: two idempotent ALTER TABLE statements + an index on user_id
for the auth gate's owner→keys lookup. Postgres-safe.
Tests: 9 backend integration tests in test_api_key_cloud_access.py
covering creation flags, the three /cloud/* fences, JWT no-op, and
deletion CASCADE; 2 frontend SettingsPage tests pinning the badge
matrix and the create-form contract; 5 daemon unit tests for the
related SpoolBuddy ssh-key sync work that landed in the same branch.
Full backend suite: 3578 passed; full frontend suite: 1597 passed; no
regressions.
Permission semantics for existing keys: keys created before this
release become "legacy" and are rejected at /cloud/* with the recreate
message. Every other endpoint they were used against — queue, status,
control — is untouched.
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.
Bambuddy can now use an external PostgreSQL database via the
DATABASE_URL environment variable. SQLite remains the default.
Dialect-aware helpers handle upserts, PRAGMAs, FTS (FTS5 vs
tsvector+GIN), backup/restore, and health checks. All migration
blocks use savepoints to prevent Postgres transaction poisoning.
Backups are always portable SQLite format regardless of backend.
Cross-database restore imports SQLite backups into PostgreSQL
with automatic boolean/datetime conversion, NOT NULL default
filling, and FK constraint handling.
Cloud credentials were stored globally — one Bambu Cloud account per
Bambuddy instance. When auth was enabled, any user logging into Cloud
overwrote everyone else's credentials. Credentials are now stored
per-user: each user gets their own independent Cloud login.
Also fixed cloud data endpoints (settings, fields, preset CRUD)
requiring settings:read/settings:update permissions instead of
cloud:auth — users who had "Cloud Auth" enabled but "Settings"
disabled couldn't load profiles after logging in.
- Replace TagDetectedModal with inline SpoolInfoCard/UnknownTagCard
in dashboard right panel (known spools show assign/sync/close,
unknown tags show add-to-inventory/link/close)
- Rewrite AssignToAmsModal as full-screen overlay reusing AmsUnitCard,
with AMS-HT and external slot support, single assignSpool API call
- Remove printer selector from assign modal (uses top bar selection)
- Extract filament_id <-> setting_id conversion to shared utility
(backend/app/utils/filament_ids.py), used by inventory + cloud routes
- Normalize slicer_filament in assign_spool to derive proper
tray_info_idx and setting_id for MQTT (was sending setting_id="")
- Rename SpoolBuddy top bar status label "Online" -> "Backend"
- Remove weightStable guard from sync weight button
Backend:
- Add dual external spool support for H2D (vt_tray as list: Ext-L/Ext-R)
- Add cloud filament ID map endpoint (/cloud/filament-id-map)
- Fix RFID spool data erased by periodic AMS updates (skip tag matcher
for RFID-tagged trays)
- Fix AMS slot config overwrites RFID spool state
- Fix K-profile selection corrupts existing profiles on X1C/P1S
- Resolve K-profiles filament name via cloud filament ID map
- Update print scheduler and usage tracker for dual external spools
Frontend:
- Add printer model filtering to ConfigureAmsSlotModal (cloud/local/builtin
presets filtered by @BBL model suffix and compatible_printers)
- Add pre-population for configured slots (preset, color, K-profile)
- Add K-Profiles view with accurate filament name resolution
- Internationalize all ConfigureAmsSlotModal strings (en/de/fr/it/ja — 21 keys)
- Add 5 new ConfigureAmsSlotModal tests (model filtering, pre-selection,
color pre-population, i18n)
- Update PrintersPage for dual external spool rendering
Docs:
- Update CHANGELOG, README, website features, and wiki AMS docs
The Bambu Cloud API returns 400 for many filament IDs (e.g. GFB01,
GFU99, GFL99), causing nozzle rack hover cards to fall back to
abbreviated tray_type values ("ASA", "TPU", "PLA") instead of full
names.
Added a built-in lookup table of 86 known Bambu filament codes as a
Phase 4 fallback in get_filament_info. Resolution order is now:
cache → cloud API → local profiles → built-in table → empty fallback.
GFB01 → "Bambu ASA", GFU99 → "Generic TPU", GFL99 → "Generic PLA",
etc. Also benefits AMS tray tooltips for unresolvable filament IDs.
1. H2C printer card was showing the H2D image — added dedicated
h2c.png and updated getPrinterImage() mapping.
2. Nozzle rack hover card showed raw filament IDs (e.g. "GFU99")
instead of human-readable names. Now resolves names via 3-tier
fallback: Bambu Cloud → local slicer profiles → raw ID.
- Frontend: nozzle rack filament_id values included in cloud
lookup query; NozzleSlotHoverCard displays resolved name.
- Backend: get_filament_info endpoint refactored from cloud-only
to cache → cloud → local profiles. Matches local presets by
setting_id in the imported OrcaSlicer JSON blob.
TOTP (Two-Factor Authentication):
- Detect TOTP vs email verification from Bambu API loginType response
- Use dedicated TFA endpoint on bambulab.com (not api.bambulab.com)
- Include browser-like headers to bypass Cloudflare protection
- Extract token from JSON response or cookies
- Frontend shows appropriate messages for each verification type
- Added i18n translations for TOTP UI (en, de, ja)
Closes#182
Printers report filament_id (e.g., GFA00) but the Bambu Cloud API expects
setting_id format which has an "S" inserted after "GF" (e.g., GFSA00).
- Add _filament_id_to_setting_id() helper function
- Transform IDs before API calls: GFx## -> GFSx##
- User presets (P-prefix) and already-correct IDs unchanged
- Improved warning message to show both original and transformed IDs
Enables checking and uploading firmware updates for printers operating
in LAN-only mode without Bambu Cloud connectivity.
Features:
- Automatic firmware version checking against Bambu Lab servers
- Orange "Update" badge on printer cards when updates available
- Firmware update modal with version info and release notes
- One-click firmware upload to printer SD card via FTP
- Real-time upload progress (actual bytes transferred)
- Step-by-step instructions for triggering update from printer
- Local firmware caching for faster re-uploads
- Supports all Bambu Lab printer models
New files:
- backend/app/services/firmware_check.py - Version checking service
- backend/app/services/firmware_update.py - Upload orchestration
- backend/app/api/routes/firmware.py - REST API endpoints
Also includes:
- FTP upload progress callback support
- 10-minute upload timeout protection
- Firmware cache directory in .gitignore
Cloud Profiles (ProfilesPage.tsx):
- Add template visibility control (showInModal flag)
- Eye/EyeOff toggle in templates modal to show/hide templates
- Only templates with showInModal=true appear in preset modals
- Default new templates to showInModal=true in save dialog
- Add preset diff/compare view with two modes:
- Compare button in edit modal (preset vs base)
- Compare mode on main page (two-preset comparison)
- Side-by-side diff with added/removed/changed highlighting
- Stats showing added/removed/changed/same counts
- Search filter and Changes/All toggle
- Type restriction (only compare same preset types)
- Fix array value display (show "value" instead of ["value"])
- Fix printer preset G-code display (format escaped \n as real newlines)
- Fix modal overflow issues with proper flex patterns
- Fix light theme colors for compare selection text