Introduces a new "Proxy Mode" for the Virtual Printer that enables
remote printing from anywhere in the world without VPN, port forwarding,
or Bambu Cloud dependency.
Bambuddy acts as a TLS relay between a remote slicer (Bambu Studio/
OrcaSlicer) and the local Bambu Lab printer:
Remote Slicer → Internet → Bambuddy Server → Local Network → Printer
The slicer connects to Bambuddy using the real printer's serial number
and access code. Bambuddy authenticates and relays all FTP (file transfer)
and MQTT (commands/status) traffic with end-to-end TLS encryption.
- No port forwarding required - printer stays safely on local network
- No VPN needed - connect from coffee shops, hotels, work, anywhere
- No Bambu Cloud dependency - fully self-hosted solution
- End-to-end TLS encryption on FTP (port 9990) and MQTT (port 8883)
- Works with Bambu Studio and OrcaSlicer
- Uses real printer credentials for authentication
- Automatic printer selection from connected printers
- Add SlicerProxyManager class for TLS relay (tcp_proxy.py)
- TLS termination with auto-generated certificates
- Concurrent FTP and MQTT proxy servers
- Connection lifecycle management with proper cleanup
- Extend VirtualPrinterManager with proxy mode support
- New 'proxy' mode alongside archive/review/queue modes
- Target printer selection and credential management
- Add proxy configuration endpoints to settings API
- Add permission checks for proxy endpoints
- Add Proxy Mode card to Virtual Printer settings
- Target printer dropdown for proxy destination
- Real-time proxy status display (ports, target, running state)
- Full i18n support (English, German)
- Add network architecture diagram
- Add proxy mode section to README
- Add comprehensive guide to wiki
- Add prominent feature section to website
- Backend unit tests for SlicerProxyManager
- Backend unit tests for proxy mode configuration
- Frontend tests for proxy mode UI components
Closes#207#170
Several frontend components were using raw fetch() instead of the API
client, causing 401 Unauthorized errors when authentication is enabled.
Changes:
- SpoolmanSettings: Use api.getSpoolmanSettings/updateSpoolmanSettings
- ProjectsPage: Use api.importProjectFile for ZIP imports
- ProjectDetailPage: Use api.exportProjectZip for exports
- CameraPage/EmbeddedCameraViewer: Use api.getCameraStatus
Added to API client:
- getSpoolmanSettings()
- updateSpoolmanSettings()
- importProjectFile()
- exportProjectZip()
- getCameraStatus()
Added tests for auth token handling in client.test.ts
Fixed VirtualPrinterSettings test expectations to match i18n strings
Closes#231
Both frontend and backend were blocking printers that already had any
smart plug linked, preventing users from adding multiple HA entities
to the same printer.
Changes:
- Frontend: Only filter out printers with existing Tasmota plugs
- Backend: Only check for duplicate Tasmota plugs on create/update
- HA entities (switches, scripts, lights, etc.) can now be linked
multiple times to the same printer for different automations
- Tasmota plugs remain limited to one per printer (physical device)
- Restored "Show on Printer Card" toggle for HA entities
- Fixed printer card only showing script.* entities; now shows all
HA entities with the toggle enabled
- HA entities now default to auto_on=False and auto_off=False
- Printer cards now update immediately when HA entities change
Closes#214
Load project relationship in ArchiveService.get_archive() alongside
created_by. Async SQLAlchemy doesn't support lazy loading, so project
must be eagerly loaded for archive_to_response() to access project.name.
Issue #233: Respect user date/time format in queue scheduler
- Replace native datetime-local input with custom text inputs
- Add date parsing/formatting utilities for US, EU, ISO, and system formats
- Add calendar button that opens native picker for convenience
- Pass dateFormat and timeFormat settings to ScheduleOptionsPanel
- Show format-appropriate placeholders (MM/DD/YYYY, DD/MM/YYYY, etc.)
Closes#233
The filament statistics were under-reporting totals because the quantity
field was not being multiplied with filament_used_grams. When users
printed multiple items (quantity > 1), only the base filament amount
was counted instead of the total.
Closes#229
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
Add missing Authorization header to fetch() calls that bypass the
request() helper. These endpoints returned 401 when auth was enabled
because the JWT token wasn't being sent with the request.
Affected functions in frontend/src/api/client.ts:
- downloadSupportBundle (support bundle download)
- downloadPrinterFilesAsZip (printer file downloads)
- exportArchives (archive CSV/XLSX export)
- exportStats (statistics CSV/XLSX export)
Closes#231
Both frontend and backend were blocking printers that already had any
smart plug linked, preventing users from adding multiple HA entities
to the same printer.
Changes:
- Frontend: Only filter out printers with existing Tasmota plugs
- Backend: Only check for duplicate Tasmota plugs on create/update
- HA entities (switches, scripts, lights, etc.) can now be linked
multiple times to the same printer for different automations
- Tasmota plugs remain limited to one per printer (physical device)
- Restored "Show on Printer Card" toggle for HA entities
- Fixed printer card only showing script.* entities; now shows all
HA entities with the toggle enabled
- HA entities now default to auto_on=False and auto_off=False
- Printer cards now update immediately when HA entities change
Closes#214
Load project relationship in ArchiveService.get_archive() alongside
created_by. Async SQLAlchemy doesn't support lazy loading, so project
must be eagerly loaded for archive_to_response() to access project.name.
Issue #233: Respect user date/time format in queue scheduler
- Replace native datetime-local input with custom text inputs
- Add date parsing/formatting utilities for US, EU, ISO, and system formats
- Add calendar button that opens native picker for convenience
- Pass dateFormat and timeFormat settings to ScheduleOptionsPanel
- Show format-appropriate placeholders (MM/DD/YYYY, DD/MM/YYYY, etc.)
Closes#233
The filament statistics were under-reporting totals because the quantity
field was not being multiplied with filament_used_grams. When users
printed multiple items (quantity > 1), only the base filament amount
was counted instead of the total.
Closes#229
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
Add missing Authorization header to fetch() calls that bypass the
request() helper. These endpoints returned 401 when auth was enabled
because the JWT token wasn't being sent with the request.
Affected functions in frontend/src/api/client.ts:
- downloadSupportBundle (support bundle download)
- downloadPrinterFilesAsZip (printer file downloads)
- exportArchives (archive CSV/XLSX export)
- exportStats (statistics CSV/XLSX export)
Closes#231
Proxy mode changes:
- Replace transparent TCP proxy with TLS-terminating proxy
- Slicer connects to Bambuddy cert, Bambuddy connects to printer
- Use real printer's serial number for SSDP and certificate
- This ensures MQTT topic subscriptions match the real printer
The proxy now:
1. Accepts TLS from slicer using Bambuddy's certificate
2. Opens TLS connection to real printer
3. Forwards decrypted data bidirectionally
Also: Complete i18n localization for VirtualPrinterSettings component
When a spool is already linked in Spoolman, the FilamentHoverCard now shows
"Open in Spoolman" button instead of "Link to Spoolman". This allows users
to quickly navigate to the spool's page in Spoolman for editing.
Changes:
- Add GET /api/v1/spoolman/spools/linked endpoint returning tag->spool_id map
- FilamentHoverCard shows "Open in Spoolman" when linkedSpoolId is set
- "Link to Spoolman" only shows when spool is not linked
- Fix unlinked spools detection to strip JSON quotes from empty tags
- Add toast notifications for link success/failure
- Invalidate linked-spools query after linking
- Add backend tests for linked spools endpoint
- Add frontend tests for LinkSpoolModal
Closes#210
Add explanatory comment for CodeQL alert about clear-text storage
of JWT secret. This is intentional and secure:
- JWT secrets must be readable by the application
- File permissions set to 0600 (owner read/write only)
- Standard practice for self-hosted apps (same as .env files)
The alert should be dismissed in GitHub Security tab as "Won't fix".
Add explanatory comment for CodeQL alert about clear-text storage
of JWT secret. This is intentional and secure:
- JWT secrets must be readable by the application
- File permissions set to 0600 (owner read/write only)
- Standard practice for self-hosted apps (same as .env files)
The alert should be dismissed in GitHub Security tab as "Won't fix".
The File Manager (Library) backend had no permission enforcement - endpoints were returning data to any authenticated user regardless of their group permissions.
Closes#224
Features:
- Add location filter for "Any {Model}" queue assignments
- Queue items can target a specific location (e.g., "Any X1C in Workshop")
- Location dropdown filter on Queue page to view jobs by location
- Scheduler considers location when assigning model-based jobs
Closes#220
## Summary
Address two critical security issues reported via GitHub Security Advisory:
1. Hardcoded JWT secret key allowing token forgery
2. Missing authentication on 77+ API endpoints
## Changes
### JWT Secret Key (backend/app/core/auth.py)
- Remove hardcoded secret "bambuddy-secret-key-change-in-production"
- Load secret from JWT_SECRET_KEY environment variable (recommended)
- Fall back to .jwt_secret file in data directory (auto-generated)
- Generate cryptographically secure 64-byte random secret if neither exists
- File is created with 0600 permissions for security
### API Authentication Middleware (backend/app/main.py)
- Add HTTP middleware that enforces auth on ALL /api/ routes
- When auth is enabled, every API request requires valid JWT or API key
- Only exempt routes that must be public:
- /api/v1/auth/status (check if auth enabled)
- /api/v1/auth/login (login endpoint)
- /api/v1/updates/version (version check)
- /api/v1/ws/* (WebSockets handle own auth)
### Test Updates
- backend/tests/conftest.py: Patch middleware's async_session for tests
- backend/tests/integration/test_ownership_permissions.py: Add missing
auth headers to requests that now require authentication
## Migration Notes
- Existing JWT tokens will be invalidated (users must re-login)
- Set JWT_SECRET_KEY env var in production for token persistence across restarts
- No database changes required
Fixes: GHSA-gc24-px2r-5qmf
Security: CWE-306 (Missing Authentication), CWE-321 (Hardcoded Crypto Key)
Closes GHSA-gc24-px2r-5qmf
The ams_mapping2 format was incorrectly using the tray_id (254/255) as
the slot_id for external spools. The printer expects slot_id to be the
actual slot index (0 for main nozzle, 1 for deputy nozzle), not the
tray_id value.
Before: {"ams_id": 255, "slot_id": 254} <- invalid slot index
After: {"ams_id": 255, "slot_id": 0} <- correct slot index
This caused prints using external spool to fail immediately with error
code 07FF_8007.
Closes#213
The ams_mapping2 format was incorrectly using the tray_id (254/255) as
the slot_id for external spools. The printer expects slot_id to be the
actual slot index (0 for main nozzle, 1 for deputy nozzle), not the
tray_id value.
Before: {"ams_id": 255, "slot_id": 254} <- invalid slot index
After: {"ams_id": 255, "slot_id": 0} <- correct slot index
This caused prints using external spool to fail immediately with error
code 07FF_8007.
Closes#213
The ams_mapping2 format was incorrectly using the tray_id (254/255) as
the slot_id for external spools. The printer expects slot_id to be the
actual slot index (0 for main nozzle, 1 for deputy nozzle), not the
tray_id value.
Before: {"ams_id": 255, "slot_id": 254} <- invalid slot index
After: {"ams_id": 255, "slot_id": 0} <- correct slot index
This caused prints using external spool to fail immediately with error
code 07FF_8007.
Closes#213