Closes the recurring "I added the printer but it isn't connecting" +
"where is X feature" cluster that drove ~1/3 of invalid-tagged issues.
New users now see a friendly welcome modal on first load and can take
a guided walkthrough that highlights the load-bearing UI surface
step-by-step.
Backend: two new nullable columns on users (onboarding_status
VARCHAR(64), onboarding_snoozed_until TIMESTAMP) added via inline
migration with a one-shot backfill that marks every existing user as
dismissed_at_migration -- pre-existing installs never see the welcome
modal. GET + PATCH /api/v1/users/me/onboarding round-trip the state;
OnboardingUpdate schema rejects dismissed_at_migration from the API
(migration-internal only), validates the tour_in_progress:<step_id>
form, and enforces snooze coherence (snoozed_until required only when
status is snoozed). _users_column_exists helper mirrors
_api_keys_column_exists and gates the backfill so restarts after
new-user signups do not clobber the welcome-eligible NULL state.
SQLite and Postgres both verified end-to-end.
Frontend architecture: OnboardingProvider wraps the app inside
AuthProvider; reads from the backend when auth is on, falls back to
localStorage (bambuddy.onboarding_status + onboarding_snoozed_until)
when auth is off so no-auth installs still get the welcome
experience. A loadFailed gate prevents the welcome modal from popping
over a backend outage -- we cannot distinguish "new user" from "GET
errored" so we stay silent. OnboardingFlow driver picks between the
Phase 0 welcome modal, the Phase 0.2 about modal, and the
step-by-step TourEngine based on persisted status. Mounted inside
BrowserRouter so the route guard's useLocation has its context (a
sibling-of-Router placement crashed at runtime).
Route guard: the overlay never renders on /setup, /login,
/spoolbuddy/*, /camera/*, /overlay/*, or while requiresSetup is
true -- fresh installs walk through the existing /setup flow
uninterrupted, and the SpoolBuddy kiosk / OBS overlay / camera-popout
windows never get a modal slapped over them.
Tour engine: 25 steps targeting existing data-tour anchors --
add-printer -> verify-connection -> printer-card sub-tour x5 (status
row, AMS row, camera, controls, customize menu) -> add-spool ->
bambu-cloud-sync -> sidebar overview x6 -> vp -> slicer-api ->
makerworld -> obico -> integrations -> notifications -> users ->
groups -> sso -> outro. Per-step route navigation via useNavigate,
anchor polling at 100ms intervals with a 3-second cap (pages need a
beat after navigation; never spin forever), box-shadow dimmed-
spotlight cutout that pointer-events-through so the user can still
interact, smart modal positioning (sidebar anchors to the right;
page anchors below or flipped above based on viewport room),
Back/Next/Skip with Escape as Skip, persistence on every Back/Next
so mid-tour reloads resume at the same step.
Conditional skip: each step exposes a skipIf(ctx) evaluated against
at least one printer exists; verify-connection + the entire card
sub-tour skip when no printer exists; makerworld skips when the user
lacks makerworld:view (the sidebar entry is permission-gated and the
anchor would not resolve); users / groups / sso skip when auth is
off. Pre-render gate means the user never sees a flash of a step
that is about to skip.
Phase 1.1 "Lock the front door" auth step explicitly removed from
the live tour -- /setup already prompts for the auth choice on fresh
installs, and users who deliberately chose no-auth must not be
nudged to enable it. The auth-card anchor stays for any future
privacy-checkup surface.
BB mascot: hero pose + 5 named poses (started / walk / almost /
allset / help) sliced from the character sheet via Pillow into
public/img/bb_*.webp. MascotIcon component takes a pose prop;
per-step pose mapping in tourSteps.ts. TourLauncher sits in the
sidebar footer as a BB icon that relaunches the tour from step 0
and consumes the [data-tour="help-icon"] selector.
WikiHelpIcon component lands the per-page question-mark icon on
Queue / Archives / Stats / Maintenance / Files / Projects /
Inventory -- opens the matching wiki.bambuddy.cool/{path}/ page in
a new tab (chose new-tab over iframe because the wiki sets
X-Frame-Options: DENY).
27 new data-tour anchors across PrintersPage / SettingsPage /
ProfilesPage / InventoryPage / Layout. i18n: 138 new keys under a
new onboarding.* namespace, real translations in every locale
(de/en/es/fr/it/ja/ko/pt-BR/tr/zh-CN/zh-TW) -- no
IDENTICAL_TO_EN_ALLOWED entries added.
Backend tests: 12 in TestOnboardingAPI cover the round-trips and the
validator branches. Frontend tests: 61 across 7 files -- anchor
presence backstop, Phase 0 modal interaction, tour engine + step
helpers, provider state machine (auth-on/off split, PATCH error
fall-through, localStorage round-trip), route guard for every
suppress path, WikiHelpIcon href/target/aria. Backend pytest
5723/5723; frontend vitest 2154/2154; ESLint clean; frontend build
clean; i18n parity clean at 5233 leaves x 11 locales.
Companion docs: docs/onboarding-tour-plan.md carries the design
(Phase 0-5 step inventory, anchor selector list, state model, asset
inventory, resolved decisions, Implementation Status section that
is authoritative for current shipped state).
ThreeMFParser._parse_3dmodel left XML-escaped values raw, so a Title of
"PCB Vise & Solder Station" landed in the DB as the literal "&" and
React re-escaped it on render to "&amp;". Apply the same
loop-until-stable html.unescape() the sibling ProjectPageParser already
uses, uniformly across all <metadata> values.
Same drop: rewrite the VP archive-name-source tooltip in all 11 locales.
BambuStudio 2.7.x (PrintJob.cpp:314-325) overwrites the user-typed
Send-dialog name with the slugified 3MF Title field whenever one is
present, so the previous "handy if you renamed the job in the send dialog"
copy was false. New text spells out the actual behavior; both Filename
and Metadata modes often produce the same string for that reason.
feat(spoolman-inventory): squashed feature work for rebase onto dev
Squashed all commits from feature/spoolman-inventory-ui onto a single commit
to enable a clean rebase onto dev. Original per-commit history preserved at
backup tag backup/spoolman-inventory-ui-prerebase-20260507-105721.
feat(spoolman-inventory): squashed feature work for rebase onto dev
Squashed all commits from feature/spoolman-inventory-ui onto a single commit
to enable a clean rebase onto dev. Original per-commit history preserved at
backup tag backup/spoolman-inventory-ui-prerebase-20260507-105721.
feat(oidc): add Azure Entra ID support with configurable email claim resolution
Adds two new OIDC provider fields: email_claim and require_email_verified.
The closed-source bambu_networking DLL validates TLS connection parameters
and rejects connections where the certificate doesn't match the printer's
real BBL CA certificate. The TLS-terminating proxy presented Bambuddy's
own certificate, causing X1C/X1 prints to silently fail after verify_job.
Switch to transparent TCP proxying for FTP, FileTransfer, Camera, and FTP
data — only MQTT remains TLS-terminated (required for IP rewriting). The
slicer now gets end-to-end TLS directly with the printer's real certificate.
Changes:
- SlicerProxyManager uses TCPProxy for FTP (990), FileTransfer (6000),
Camera (322), and pre-listens on FTP data ports (50000-50100)
- Only MQTT (8883) uses TLSProxy for IP rewriting
- Remove debug logging from MQTT and FTP proxy code
- Fix install.sh missing AmbientCapabilities=CAP_NET_BIND_SERVICE
- Update module docstring, migration docs, README proxy description
- Add tests verifying transparent proxy architecture
- Add ports 6000 (file transfer) and 322 (RTSP camera) to Dockerfile
EXPOSE and docker-compose.yml bridge mode port mapping
- Update migration doc with new proxy mode port requirements
- Regenerate proxy-mode-diagram.png with all proxied ports
When running multiple virtual printers with different access codes on
separate bind IPs, FTP connections were always routed to the wrong VP.
Root cause: the iptables REDIRECT rule (990→9990) rewrites the
destination IP to the incoming interface's primary address. With Linux's
weak host model (arp_filter=0), packets for secondary IPs arrive on the
primary interface, and REDIRECT sends them all to the first VP's FTP
server. MQTT was unaffected because port 8883 had no redirect.
Fix: FTP server now binds directly to port 990 (standard implicit FTPS),
eliminating the iptables redirect entirely. Requires CAP_NET_BIND_SERVICE
(already set in the systemd service file and Docker image).
Also removed a global asyncio set_exception_handler() in the MQTT server
that was overwritten by each VP instance, causing spurious "Unhandled
exception in client_connected_cb" errors on startup.
Changes:
- FTP_PORT: 9990 → 990 (ftp_server.py)
- Removed set_exception_handler() from MQTT server
- Updated Dockerfile, docker-compose.yml port mappings
- Deprecated --redirect-990 in install script
- Updated wiki: removed iptables instructions for all platforms
- Added migration guide (docs/migration-vp-ftp-port.md)
- Added unit tests for port constant and no-global-state invariant
P2S firmware sends local slot IDs (0-3) in tray_now, not global tray
IDs, when multiple AMS units are connected. The UI highlighted the
wrong AMS unit (e.g., AMS-A instead of AMS-B). Usage tracking was
unaffected because it uses the MQTT mapping field.
Added disambiguation in _handle_ams_data for single-nozzle printers
with ams_exist_bits indicating >1 AMS: cross-references tray_now with
the MQTT mapping field (snow-encoded) to resolve the correct global
tray ID. Falls back to raw value when no mapping is available or
ambiguous. No behavior change for single-AMS printers or dual-nozzle
(H2D) printers.
Multiple Virtual Printers:
- Each VP gets a dedicated bind IP with independent FTP, MQTT, SSDP, and Bind services
- New VirtualPrinter DB model, CRUD API (/api/virtual-printers), React UI
- VirtualPrinterList, VirtualPrinterCard, VirtualPrinterAddDialog components
- Per-instance TLS certificates (shared CA), 11 printer models, all 4 modes
- Auto-incremented serial suffixes, network interface override per VP
Dual Bind/Detect Ports (#445):
- Listen on both ports 3000 and 3002 for slicer bind/detect handshake
- Different BambuStudio/OrcaSlicer versions use different ports
- Applies to BindServer (server mode) and SlicerProxyManager (proxy mode)
- Updated Dockerfile, docker-compose.yml, firewall rules in wiki
Also:
- Rewrote VP test suite for new multi-instance architecture (75 tests)
- Rewritten "How it works" section with 3-step workflow explanation
- Updated all 5 locales (en, de, ja, fr, it)
- Updated wiki and website for multi-VP + dual ports
- New multi-VP screenshot
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
- Header navigation link
- Installation guide link
- Documentation section with all 5 doc links
- Footer documentation link
2. Screenshots updated with new images:
- Replaced old maintenance screenshots with maintenance-1.png and maintenance-2.png
- Replaced old profile screenshots with cloud_profiles-1.png, cloud_profiles-2.png, k_profiles-1.png, k_profiles-2.png
- Replaced old settings screenshots with settings_general.png, settings_smart_plugs.png, settings_notifications.png, settings_api_keys.png
- Removed old unused screenshot files from docs/screenshots/