mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-04 05:01:37 +02:00
5dfd38597acae0a8a380bd3e7cbeb95d1384765a
2748
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5dfd38597a |
docs(install): polish Windows section + Service/Update/Troubleshooting
entries PR #1529 added the Windows installer but the rendered README sections had a stray blank line inside the install one-liner's code fence and the cross-platform Service Management / Updating / Troubleshooting sections still only covered Linux + macOS + Docker. Fold in Windows entries (Start-Service, Get-NetTCPConnection, NSSM runtime log path) and link the README description to the Windows Installer wiki page so users know where the parameter reference and unattended examples live. |
||
|
|
4582a28866 |
Feature/windows installer (#1529)
Add feature: Added a powershells script to easily install bambuddy on windows |
||
|
|
7fa58d8689 | Housekeeping | ||
|
|
0967f9758b | test(pytest): silence upstream starlette httpx2 deprecation noise v0.2.4.4 | ||
|
|
c2ab78a69e |
Merge remote-tracking branch 'origin/0.2.4.4' into resolve-pr-1566
# Conflicts: # .github/workflows/ci.yml # Dockerfile.test |
||
|
|
b6fa77a649 | Bumped version | ||
|
|
845ad39b19 |
fix(security): GHSA-6mf4-q26m-47pv — fail-closed on auth-probe DB errors (CVSS 9.8)
is_auth_enabled() and auth_middleware both caught every exception during
the auth-state probe and returned the "allow" answer instead of denying
the request. Reporter's PoC floods /api/v1/auth/login to exhaust file
descriptors, forcing the next SQLite connect to raise, then hits a
protected endpoint during the fail-open window with no token — granting
unauthenticated access to admin-account creation, API-key creation, DB
backup download, and printer control. CWE-636 / CWE-755. Affects >= 0.1.6.
Fix:
- is_auth_enabled (backend/app/core/auth.py): only returns False for the
legitimate "settings row absent" case; any actual exception propagates
so the caller can deny the request.
- auth_middleware (backend/app/main.py): returns 503 on any probe failure
instead of await call_next(request).
4 new regression tests in test_auth_fail_closed.py pin the contract
(propagates DB exceptions, returns False for no-row, True for "true",
False for "false"). 1 existing security test renamed and updated to
accept either 500 or 503 (both fail-closed) and to verify the
SQLAlchemy detail does not leak in the body.
Codebase grep confirmed no other auth-decision predicate has the same
fail-open shape: _validate_api_key returns None on catch (→ 401 fail-
closed downstream), is_advanced_auth_enabled propagates correctly,
permissions.py has no catch-alls.
Reported by @wondercrash via private advisory.
|
||
|
|
449502cc9f | Updated BACKERS | ||
|
|
c51a9772cb |
ci: bump actions to Node-24-compatible majors
GitHub forces Node-20 actions to run on Node 24 starting 2026-06-02 and
removes Node 20 from the runner on 2026-09-16. Bumping each action to
its first Node-24 major now gets us ahead of both deadlines and silences
the deprecation warnings already firing in every CI run.
Bumps (across ci.yml, security.yml, codeql.yml, auto-label-area.yml,
issue-closed.yml, stale.yml):
- actions/checkout v4 -> v6
- actions/setup-python v5 -> v6
- actions/setup-node v4 -> v6
- actions/cache v4 -> v5
- actions/upload-artifact v4 -> v7
- actions/github-script v7 -> v9
- actions/stale v9 -> v10
- docker/setup-buildx v3 -> v4
- docker/build-push v5 -> v7
Verified each major's breaking-change notes against our usage:
- setup-node v6 limits auto-cache to npm only; we already pass
cache: 'npm' explicitly, so nothing changes.
- github-script v9 drops require('@actions/github'); none of our
scripts use it (only require('fs') and the injected github/context
globals).
- setup-buildx v4 removes deprecated inputs; we call it with no
inputs.
- build-push v6 enables build summaries by default; informational,
can disable via DOCKER_BUILD_SUMMARY=false env if it gets noisy.
codeql-action stays on v4 (already runs on Node 24). Trivy and
github-repo-stats are Docker actions and aren't affected by the
Node-20 deprecation.
|
||
|
|
c8881c165a |
chore(deps): pin fastapi<0.136.0 to dodge MAL-2026-4750
Amazon Inspector flagged fastapi 0.136.x for shipping an undocumented `fastar>=0.9.0` dep in its [standard] extras group. `fastar` is a Rust-tar binding package, no plausible reason for a web framework to depend on it. Even if `fastar` is benign today, the advisory's "namespace-abuse vector" framing is valid — whoever controls the fastar PyPI namespace gains code execution at install time across every fastapi[standard] install. Bambuddy doesn't request [standard] so we don't pull fastar in practice, but pip-audit flags the fastapi package itself and breaks CI. Hold to 0.135.x (last clean release line) until upstream removes the dep. |
||
|
|
d9536d6e5e | Updated BACKERS | ||
|
|
5d248e16ba |
chore(triage): tighten bug-report template + add Area dropdown to cut invalid-issue load
170 issues have been closed with the `invalid` label (61 of them in
the last 30 days alone — ~1 in 5 of all closed issues), almost always
because the reporter hadn't run the in-app Connection Diagnostic or
checked the documented troubleshooting page. The Connection Diagnostic
shipped weeks ago but the bug-report form let people skip it: the
"I ran it" checkbox was `required: false` and the Support Package
field was optional. Tighten both.
Form changes (.github/ISSUE_TEMPLATE/bug_report.yml):
- Connection Diagnostic checkbox: required: false → true
- Support Package field: required: false → true ("drag the .zip
or explain why you cannot attach one")
- New required textarea "Troubleshooting steps already taken" —
forces the reporter to type WHAT they tried and WHICH wiki pages
they checked before submitting. Empty answers can't submit.
- Pre-form intro spells out the search → wiki → diagnostic →
support package sequence and cites the 1-in-5 stat
- Final-checks list grew from one to three required confirmations
(searched issues + checked troubleshooting wiki + ran Connection
Diagnostic for any connection/printing/camera issue)
Bug categorization (the gap that motivated this):
- Old `Component` dropdown was Bambuddy / SpoolBuddy / Both — no
area triage signal
- Replaced with two required dropdowns:
- Product: Bambuddy / SpoolBuddy
- Area: 15 options covering the actual feature surface +
Other / not sure
- Auto-label workflow (.github/workflows/auto-label-area.yml)
reads the Area dropdown from the rendered issue body on
open/edit and applies the matching area:* label. Tolerant of
CRLF and the _No response_ placeholder, won't re-add on edit
re-fires, warns on unknown Area values
Maintainer hand-off — labels must exist BEFORE the workflow runs,
since github-script's addLabels throws on missing labels. Create the
16 labels (15 area:* + 1 area:unsorted) once via the `gh label create`
commands captured in CHANGELOG / commit context.
OS dropdown left untouched (Docker stays — per Martin).
Printer Model dropdown verified against backend/app/utils/printer_models.py
PRINTER_MODEL_MAP: all 13 current models present (X1 Carbon, X1, X1E,
X2D, P1S, P1P, P2S, A1, A1 Mini, H2D, H2D Pro, H2C, H2S).
|
||
|
|
c0129ef93f |
chore(docker): silence Trivy DS-0026 on Dockerfile.test via HEALTHCHECK NONE
Trivy raised DS-0026 ("No HEALTHCHECK defined") against Dockerfile.test
on every run of the security workflow. The test image is a one-shot
pytest runner — there's no service to probe, so any HEALTHCHECK we
invented would be cargo-cult noise that fires once and means nothing.
HEALTHCHECK NONE is the documented Docker directive to explicitly opt
out of any inherited HEALTHCHECK and is the way Trivy itself expects
projects to signal "this image is intentionally not a long-running
service." Adding it closes code-scanning alert #813 cleanly.
Note: the perl-base CVE-2026-8376 alert (#811) is left open for now
and dismissed in the GitHub UI as "Won't fix - no upstream patch"
because Debian Trixie has not yet shipped a fixed perl-base; the
patched build will land automatically on the next base-image refresh.
|
||
|
|
66fea70564 |
ci(docker): full backend suite in Docker, 4-way matrix shard, GHA cache backend
Earlier patch trimmed the duplicate unit-test re-run from docker-test
to drop a 5-10 min job that wasn't adding coverage. But "wasn't adding
coverage" only holds for pure-logic tests — system-touching tests
(ffmpeg version probes, ftp clients, subprocess shell-outs, locale/
timezone-sensitive assertions, paths) genuinely can pass on the GHA
host and fail in python:3.13-slim. Curation via a `docker_env`
marker is fragile (new tests get forgotten); gating on `main` only
defers the cost without removing it.
Instead, run the full backend suite IN Docker on every PR but make
it fast:
- New docker-backend-tests job runs the same 4-way pytest-split
matrix as the host backend-tests, just inside the test image.
- docker/setup-buildx-action + docker/build-push-action@v5 with
cache-from/cache-to: type=gha,scope=backend-test persist the
BuildKit cache (pip-install layer included) across CI runs and
across the 4 sibling shards. Cold build is ~150s/shard; warm
build drops to ~10s/shard.
- fail-fast: false so a single failing shard surfaces the rest's
output too.
Total CI wall-clock for a PR push is now gated by docker-test (the
image-build + integration HTTP smoke + integration test suite job)
at ~3 min, not by the unit-test re-run anymore.
The earlier ci.yml step that ran `docker compose run --rm
backend-test` synchronously in the docker-test job stays removed —
the new docker-backend-tests matrix covers the same ground and is
much faster.
|
||
|
|
b3b37e8f08 |
ci(docker): full backend suite in Docker, 4-way matrix shard, GHA cache backend
Earlier patch trimmed the duplicate unit-test re-run from docker-test
to drop a 5-10 min job that wasn't adding coverage. But "wasn't adding
coverage" only holds for pure-logic tests — system-touching tests
(ffmpeg version probes, ftp clients, subprocess shell-outs, locale/
timezone-sensitive assertions, paths) genuinely can pass on the GHA
host and fail in python:3.13-slim. Curation via a `docker_env`
marker is fragile (new tests get forgotten); gating on `main` only
defers the cost without removing it.
Instead, run the full backend suite IN Docker on every PR but make
it fast:
- New docker-backend-tests job runs the same 4-way pytest-split
matrix as the host backend-tests, just inside the test image.
- docker/setup-buildx-action + docker/build-push-action@v5 with
cache-from/cache-to: type=gha,scope=backend-test persist the
BuildKit cache (pip-install layer included) across CI runs and
across the 4 sibling shards. Cold build is ~150s/shard; warm
build drops to ~10s/shard.
- fail-fast: false so a single failing shard surfaces the rest's
output too.
Total CI wall-clock for a PR push is now gated by docker-test (the
image-build + integration HTTP smoke + integration test suite job)
at ~3 min, not by the unit-test re-run anymore.
The earlier ci.yml step that ran `docker compose run --rm
backend-test` synchronously in the docker-test job stays removed —
the new docker-backend-tests matrix covers the same ground and is
much faster.
v0.2.4.3
|
||
|
|
1b271a8ead |
ci(docker): stop re-running unit tests inside the test image
The "Docker Build" job in ci.yml was running the same 5287 backend tests + 2022 frontend tests inside the bambuddy-backend-test / bambuddy-frontend-test images that the host-side backend-tests and frontend-tests jobs had already run. Same test code, same Python version (env.PYTHON_VERSION), same requirements.txt the test image installs. On 2-vCPU GHA runners that re-run added 5-10 min of wall-clock for zero new coverage — and "frontend tests in Docker" added another 2-3 min for the same reason. Drop both steps from the CI job. Keep everything that validates the Docker IMAGE specifically: production image build, backend module import verification, static-files-copied check, integration container bring-up + health/API/static HTTP smoke checks, and the integration test suite (which IS genuinely Docker-specific — it runs against the live container via BAMBUDDY_TEST_URL). test_docker.sh keeps the unit-test reruns because devs running it locally don't have a separate host-side pytest job to compare against. Combined with the earlier 4-way pytest-split shard on the host backend-tests job, expected PR-push wall-clock drops from ~10-12 min to ~3 min, gated on max(backend-tests shard, frontend tests, docker-image-build+integration). |
||
|
|
ed905d8406 |
ci(docker): stop re-running unit tests inside the test image
The "Docker Build" job in ci.yml was running the same 5287 backend tests + 2022 frontend tests inside the bambuddy-backend-test / bambuddy-frontend-test images that the host-side backend-tests and frontend-tests jobs had already run. Same test code, same Python version (env.PYTHON_VERSION), same requirements.txt the test image installs. On 2-vCPU GHA runners that re-run added 5-10 min of wall-clock for zero new coverage — and "frontend tests in Docker" added another 2-3 min for the same reason. Drop both steps from the CI job. Keep everything that validates the Docker IMAGE specifically: production image build, backend module import verification, static-files-copied check, integration container bring-up + health/API/static HTTP smoke checks, and the integration test suite (which IS genuinely Docker-specific — it runs against the live container via BAMBUDDY_TEST_URL). test_docker.sh keeps the unit-test reruns because devs running it locally don't have a separate host-side pytest job to compare against. Combined with the earlier 4-way pytest-split shard on the host backend-tests job, expected PR-push wall-clock drops from ~10-12 min to ~3 min, gated on max(backend-tests shard, frontend tests, docker-image-build+integration). |
||
|
|
e377b4aaa9 |
ci(docker): drop -v, -n auto instead of -n 30, pip cache mount
Three things were making the Docker test runs noisier and slower than
they needed to be:
1. -v was hardcoded in Dockerfile.test:35 CMD and in docker-compose.
test.yml's integration-test-runner command. The ci.yml change to
drop -v from the bare pytest call missed both — Docker runs use
the image's CMD, not the workflow's.
2. -n 30 was hardcoded as the xdist worker count. On a 2-vCPU CI box
that's 30 Python processes fighting over 2 cores — mostly IPC and
import-thrash overhead. -n auto adapts to the host: 2 on CI, 30
on a 30-core dev box. Same final-result throughput on the dev
box, much better on small runners.
3. pip install had --no-cache-dir and no BuildKit cache mount, so
every Docker build re-fetched ~50 packages from PyPI (~60-90s
on a cold pip cache). Adding `RUN --mount=type=cache,target=
/root/.cache/pip` (with the `# syntax=docker/dockerfile:1.7`
directive that enables it) makes subsequent builds re-use the
download cache so they only do install work, ~5s instead of
~90s. DOCKER_BUILDKIT=1 is already exported in test_docker.sh
and is the GHA default since runner image 2023, so the cache
mount is always honoured.
Verified locally: Docker build is 19s warm (was ~90s cold each
time), test run is 102s with 5287 passed / 1 skipped (the
by-design spoolbuddy importorskip) — clean output, no [gwN]
worker spam, no "created: 30/30 workers" startup line.
GHA-side per-run cold-build slowness still happens because GHA
runners are ephemeral; a follow-up using docker/build-push-action
with type=gha cache backend would persist the BuildKit cache
across CI runs but that's a bigger workflow change.
|
||
|
|
39a075918a |
ci(docker): drop -v, -n auto instead of -n 30, pip cache mount
Three things were making the Docker test runs noisier and slower than
they needed to be:
1. -v was hardcoded in Dockerfile.test:35 CMD and in docker-compose.
test.yml's integration-test-runner command. The ci.yml change to
drop -v from the bare pytest call missed both — Docker runs use
the image's CMD, not the workflow's.
2. -n 30 was hardcoded as the xdist worker count. On a 2-vCPU CI box
that's 30 Python processes fighting over 2 cores — mostly IPC and
import-thrash overhead. -n auto adapts to the host: 2 on CI, 30
on a 30-core dev box. Same final-result throughput on the dev
box, much better on small runners.
3. pip install had --no-cache-dir and no BuildKit cache mount, so
every Docker build re-fetched ~50 packages from PyPI (~60-90s
on a cold pip cache). Adding `RUN --mount=type=cache,target=
/root/.cache/pip` (with the `# syntax=docker/dockerfile:1.7`
directive that enables it) makes subsequent builds re-use the
download cache so they only do install work, ~5s instead of
~90s. DOCKER_BUILDKIT=1 is already exported in test_docker.sh
and is the GHA default since runner image 2023, so the cache
mount is always honoured.
Verified locally: Docker build is 19s warm (was ~90s cold each
time), test run is 102s with 5287 passed / 1 skipped (the
by-design spoolbuddy importorskip) — clean output, no [gwN]
worker spam, no "created: 30/30 workers" startup line.
GHA-side per-run cold-build slowness still happens because GHA
runners are ephemeral; a follow-up using docker/build-push-action
with type=gha cache backend would persist the BuildKit cache
across CI runs but that's a bigger workflow change.
|
||
|
|
fdf818e54c |
fix(test): stop sys.modules-deleting backend.app.main in test_code_quality
+ ci: shard backend tests 4-way + drop -v for ~3.5x wall-clock speedup Root cause of the 4 CI failures on PR #1514 (all in test_print_start_assigns_printer_id_to_vp_archive.py + test_timelapse_baseline_restart_recovery.py): test_all_modules_importable in test_code_quality.py was deleting backend.app.main from sys.modules and re-importing it via importlib.import_module. That created NEW module-level dicts (_timelapse_baselines, _expected_prints, _active_prints, …) and re-ran root_logger.addHandler — hence the duplicate log lines at the same microsecond in captured stderr. Any sibling test that bound those names via "from backend.app.main import _timelapse_baselines" before the reimport now held a reference to the OLD dict; production code (reached via "from backend.app.main import on_print_start") resolved the symbol through the NEW module instance. Production mutated the new dict, the test read the old one, the assertion saw None / un-mutated mock_archive. Locally with -n 30, xdist load-balanced test_code_quality.py to a different worker process so the collision never happened (which is why the suite was green for me). CI's -n auto = -n 2 on ubuntu-latest made the collision deterministic. Fix: drop the "del sys.modules[name]" step. importlib.import_module already returns the cached module if cached, or runs the import machinery if not — either way, any import-time error surfaces. The "fresh import" framing was theatre; in practice every module in the list is already imported by other tests/fixtures before this test runs, so we were never actually getting a fresh import anyway — just destruction. CI workflow tightening (separate concern, same PR since both touch the test infrastructure): - Dropped -v from the pytest invocation. 5300+ "PASSED foo::bar" lines per worker were eating ~30-60s of stdout I/O on 2-vCPU runners. --tb=short is sufficient for failure context. - Sharded backend-tests into a 4-way matrix via pytest-split (new dev dep). Each shard runs ~1326 tests in ~95s on a 2-vCPU runner; all 4 run in parallel so wall-clock drops from 362s -> ~100s. - fail-fast: false on the matrix so a single failing shard doesn't hide failures in the other three — PRs see the complete failure picture in one push. |
||
|
|
4fac9ff12c |
fix(test): stop sys.modules-deleting backend.app.main in test_code_quality
+ ci: shard backend tests 4-way + drop -v for ~3.5x wall-clock speedup Root cause of the 4 CI failures on PR #1514 (all in test_print_start_assigns_printer_id_to_vp_archive.py + test_timelapse_baseline_restart_recovery.py): test_all_modules_importable in test_code_quality.py was deleting backend.app.main from sys.modules and re-importing it via importlib.import_module. That created NEW module-level dicts (_timelapse_baselines, _expected_prints, _active_prints, …) and re-ran root_logger.addHandler — hence the duplicate log lines at the same microsecond in captured stderr. Any sibling test that bound those names via "from backend.app.main import _timelapse_baselines" before the reimport now held a reference to the OLD dict; production code (reached via "from backend.app.main import on_print_start") resolved the symbol through the NEW module instance. Production mutated the new dict, the test read the old one, the assertion saw None / un-mutated mock_archive. Locally with -n 30, xdist load-balanced test_code_quality.py to a different worker process so the collision never happened (which is why the suite was green for me). CI's -n auto = -n 2 on ubuntu-latest made the collision deterministic. Fix: drop the "del sys.modules[name]" step. importlib.import_module already returns the cached module if cached, or runs the import machinery if not — either way, any import-time error surfaces. The "fresh import" framing was theatre; in practice every module in the list is already imported by other tests/fixtures before this test runs, so we were never actually getting a fresh import anyway — just destruction. CI workflow tightening (separate concern, same PR since both touch the test infrastructure): - Dropped -v from the pytest invocation. 5300+ "PASSED foo::bar" lines per worker were eating ~30-60s of stdout I/O on 2-vCPU runners. --tb=short is sufficient for failure context. - Sharded backend-tests into a 4-way matrix via pytest-split (new dev dep). Each shard runs ~1326 tests in ~95s on a 2-vCPU runner; all 4 run in parallel so wall-clock drops from 362s -> ~100s. - fail-fast: false on the matrix so a single failing shard doesn't hide failures in the other three — PRs see the complete failure picture in one push. |
||
|
|
b92bdb7d09 |
fix(test): snapshot _timelapse_baselines inside the patch context to dodge CI race
test_running_observed_captures_baseline_on_restart_recovery was reading _timelapse_baselines.get(1) after the patch() with-block exited. Locally and under low parallelism this works fine — the dict still holds what _capture_timelapse_baseline_at_start wrote. CI under xdist's default load-balancing scheduling intermittently saw the dict empty by the time the top-level assert ran, even though the production code logged "Baseline at print start: 3 video files for printer 1" right before returning. The duplicate log line at the same microsecond in the captured stderr is the tell — module state is being re-touched between the handler completing and the test asserting, almost certainly via the session-scoped event_loop fixture in conftest.py interacting badly with the per-file autouse _clear_baselines teardown of a sibling test on the same worker. The test is verifying the handler captured the baseline at the moment it returned, so capture the relevant value at exactly that point — inside the with-block, immediately after the await. That's immune to whatever happens to the module-level dict afterward. |
||
|
|
af03a6f384 |
fix(test): snapshot _timelapse_baselines inside the patch context to dodge CI race
test_running_observed_captures_baseline_on_restart_recovery was reading _timelapse_baselines.get(1) after the patch() with-block exited. Locally and under low parallelism this works fine — the dict still holds what _capture_timelapse_baseline_at_start wrote. CI under xdist's default load-balancing scheduling intermittently saw the dict empty by the time the top-level assert ran, even though the production code logged "Baseline at print start: 3 video files for printer 1" right before returning. The duplicate log line at the same microsecond in the captured stderr is the tell — module state is being re-touched between the handler completing and the test asserting, almost certainly via the session-scoped event_loop fixture in conftest.py interacting badly with the per-file autouse _clear_baselines teardown of a sibling test on the same worker. The test is verifying the handler captured the baseline at the moment it returned, so capture the relevant value at exactly that point — inside the with-block, immediately after the await. That's immune to whatever happens to the module-level dict afterward. |
||
|
|
f385936c58 |
Merge pull request #1514 from maziggy/0.2.4.3
**Bambuddy v0.2.4.3** **⚠ Upgrade Notes — Read Before Updating** Almost everyone is upgrading from 0.2.4.2. 0.2.4.3 is a patch release on the same 0.2.4 code base — no schema breaks, no Docker entrypoint changes, no Vite/proxy quirks. The in-app Apply Update button in Settings → System → Updates works for Docker and for any native install already on 0.2.4.1 or later. Make a backup before upgrading via Settings → Backup → Create Backup. Native install with update.sh snapshots the database automatically and rolls back on failure. Docker and fully-manual paths don't. ### Docker docker compose pull docker compose up -d docker-compose.yml doesn't need refreshing — none of the entrypoint, volume, or env-var conventions changed since 0.2.4. ### Native install — recommended path sudo BRANCH=main /opt/bambuddy/install/update.sh Snapshots the database first and rolls back on failure. ### Native install — manual path sudo systemctl stop bambuddy cd /opt/bambuddy sudo -u bambuddy git fetch --prune --tags --force origin sudo -u bambuddy git checkout main sudo -u bambuddy git reset --hard origin/main sudo /opt/bambuddy/venv/bin/pip install -r requirements.txt sudo systemctl start bambuddy **Behaviour changes to know about** - "Prefer Lowest Remaining Filament" now reads from your inventory, not the printer's RFID counter. Slots bound to a Bambuddy or Spoolman spool sort by your remaining-grams figure first; MQTT-only slots fall back to the previous logic. If you use the preference and have bound spools, the dispatch will start picking a different slot than before — the one that actually has less filament left in your inventory. (#1508) - Camera live view: ffmpeg's RTSP socket-timeout flag is now probed at runtime. Native-Ubuntu installs with a transitional-era ffmpeg were hitting Address already in use because that ffmpeg repurposed -timeout into a listen-mode option. No user action — but if you'd manually patched your install to drop the flag, you can revert. (#1504) - Cloudflare-fronted installs: CSP script-src is now nonce-based. If you'd added 'unsafe-inline' to your reverse-proxy CSP as a workaround for Cloudflare's auto-injected Rocket Loader / email-obfuscation script, remove it — it's no longer needed. (#1460 follow-up) - The 0.2.4 feature-freeze still holds for 0.2.5-bound work. A handful of feat: items shipped in this release because they were already in flight before the freeze and would have stranded the original reporters until the 0.2.5 cycle (Spanish locale #1243, the slicer-filter series #1325, cross-printer re-slicing). Everything else from now on is fix-only on the 0.2.4 train. --- **Highlights** 0.2.4.3 closes two long-running issue clusters and lands the self-service triage infrastructure the support queue has been asking for. The #1325 slicer-filter series is finally complete — Process / Filament dropdowns in the Slice dialog now filter by your selected printer and nozzle diameter, with the right answer whether you've uploaded a Slicer Bundle or are relying on the @BBL preset-name fallback. The #1395 camera reliability series picks up P2S stream stability, ffmpeg stderr capture on stalls, and the cross-ffmpeg-version RTSP timeout flag that was breaking live view on native Ubuntu (#1504). Around them: a new Connection Diagnostic for "printer won't connect" triage, a Log Health Scanner that surfaces self-fixable issues before they hit the support queue, and a Virtual Printer Setup Diagnostic with one-click slicer-cert export — all three are now auto-attached (sanitized) to support bundles and bug reports. Plus the largest fix tally of any 0.2.4.x patch — re-slicing correctness across nozzle classes and multi-plate sources (#1493 + the slice-all toggle), "Prefer Lowest Filament" now reads from your inventory instead of the printer's RFID counter (#1508), timelapse re-attaches after a backend restart mid-print (#1485 follow-up), and a full Spanish locale (#1243). Security: idna >= 3.15 for CVE-2026-45409 (ReDoS) and starlette >= 1.0.1 for PYSEC-2026-161 (malformed Host header). --- **New Features** - Connection Diagnostic — self-service triage for "printer won't connect / won't print" — Triage review of recently-closed issues found roughly a third were user-side setup errors (wrong IP after DHCP renew, LAN-mode toggle off, access code mismatch, port-block by a router-level guest network). The new per-printer diagnostic runs the full layer-1-through-MQTT check in one click — TCP reachability, MQTT TLS handshake, status-message receipt, IP sanity, SSDP visibility — and renders a ranked layer-8 root-cause list with a "Copy to clipboard for bug report" affordance. - Log Health Scanner + Add/Edit-Printer setup pre-flight — Passive scanner that walks your last N hours of logs against a curated catalog of known-issue signatures (#1480 FTP loop, #1395 ffmpeg drops, #1462 drying-power-off, …) and surfaces matches on the System page. Add/Edit-Printer also runs a pre-save pre-flight that warns before you commit a misconfiguration (e.g. trailing whitespace in the access code, swapped IP/serial). - Virtual Printer Setup Diagnostic + one-click slicer-certificate export — VP card now exposes a per-VP "Diagnose" button that walks the slicer-side misconfig list (cert not installed in BambuStudio's trust root, FTPS port collision with another service on the bind IP, MQTT-mode mismatch with the target printer). The matching "Export slicer certificate" button writes the right .crt for whichever VP you're testing, eliminating the manual openssl invocation that ate half the VP-related support tickets. - Slicer: process & filament profiles now filter by the selected printer (#1325, requested by @IndividualGhost1905) — Picking an X1C in the server-side Slice dialog no longer floods the Process / Filament dropdowns with H2D-only presets. When you've uploaded a Slicer Bundle for that printer, filtering is exact-match against the bundle's contents; without a bundle, falls back to a name-based @BBL heuristic on model + nozzle. - Slicer: cross-printer re-slicing across nozzle classes + multi-plate slice-all toggle — Re-slicing an X1C archive for an H2D, or re-slicing a parted-statue multi-plate 3MF in one click. The slice-all toggle produces a single output 3MF whose Metadata/plate_N.gcode entries cover every plate, with the filament dropdowns showing the union of slot usage across plates so paint slots from plate 2 aren't invisible. - Spanish (es) locale (#1243, requested by @MiguelAngelLV) — Full European Spanish translation across all 4899 keys. Joins the existing en / de / fr / it / ja / pt-BR / zh-CN / zh-TW locales; parity check enforces no English-leak. - Currency: Belize Dollars (BZD) (#1454, requested by @PLGuerraDesigns) — Added to the Settings → Cost currency dropdown. - Event-loop stall watchdog — makes a frozen backend self-diagnose (#1486 groundwork) — Several "container hangs after adding a printer" reports share a signature that leaves no actionable evidence in the logs. The watchdog logs a full traceback when the asyncio event loop is blocked for longer than the threshold, so the next time it hangs we get a real stacktrace instead of "it just stopped responding". --- **Changes** - Support bundle + bug-report submission now include the live diagnostic snapshot. Connection Diagnostic / Virtual Printer Setup Diagnostic / Log Health Scanner results are now persisted into the downloadable support ZIP and the submitted GitHub issue, with IPs / printer names / serials / access codes sanitized via the existing log-sanitizer plus an IPv4 regex fallback. A 4-step progress checklist replaces the spinner so the longer wait is communicated honestly. - Settings → SpoolBuddy: CPU load tile on the device card. Pulled from the existing system_stats.load_avg heartbeat — no new round-trip. - Filament inventory: grouped rows now show group totals (#1368) — Collapsed group row now shows the rolled-up Remaining / Total values for the group instead of the values from a single member spool. - Bug-report panel: connection diagnostic collapses for multi-printer setups so the panel doesn't overflow when you have eight printers. - Color Catalog sync now identifies itself as Bambuddy to filamentcolors.xyz. Honest User-Agent — Bambuddy/<version> instead of the bare python-httpx/x.y.z default. Same compliance-audit pass that produced today's Bambu Lab outreach. --- **Fixed** Slicer / slicing - Slicer process / filament dropdowns now filter by nozzle diameter too (#1325 follow-up #2, reported by @IndividualGhost1905) — With the @BBL name fallback in place, an X2D 0.4 selection was still admitting 0.2 / 0.6 / 0.8 nozzle process variants. The regex now parses both model AND nozzle from preset names; differing nozzles fall into the "Other printers" group. - Slicer process / filament dropdowns now filter for users without uploaded Slicer Bundles (#1325 follow-up, reported by @IndividualGhost1905) — Original #1325 fix relied on the user having uploaded a .bbscfg; reporters running without one saw no filtering at all. The name-based @BBL fallback now produces sensible filtering even when no bundle is present. - Slicer: process / filament dropdowns now filter by printer using uploaded Slicer Bundles (#1325, reported by @IndividualGhost1905) — Root fix for #1325 — replaces the preset-name guessing heuristic with bundle-backed exact match. - Re-slicing across the single-nozzle / dual-nozzle boundary now actually works (#1493) — Re-slicing a model sliced for a single-nozzle printer (X1C, P1S, A1, P2S, …) onto a dual-nozzle printer (H2D, X2D) — or vice versa — now produces a correct output 3MF. Per-plate loop handles the bed-bound consolidation that BS CLI's --arrange would otherwise reject. - Failed slice now opens an error modal instead of a toast that vanishes before it can be read. - Re-slicing for a different printer no longer silently produces a file for the original printer — Cross-model re-slice was carrying the source's printer_id through, so the resulting archive looked like it belonged to the wrong printer in the UI. - Re-sliced archive now shows the printer it was sliced for, not the source's printer. - Sliced files no longer report "0 g" filament usage — Archive card and slice-result both showed filament_used_g: 0 for real multi-hour prints. - Flow Calibration now actually runs when the print option is enabled (#1478, reported by @andreirusu99) — H2S reporter saw poor extrusion around corners; root cause was the Flow Calibration option being silently skipped because the wrong project_file field was being passed to the printer. - File Manager: list-view actions no longer clipped; preview modal Slice button now respects the Slicer API setting. Camera - X1/H2/P2 live camera no longer fails with Address already in use on transitional ffmpeg builds (#1504, reported by @rage03usa, confirmed by @PawseHaxor) — Native-Ubuntu reporter was retrying indefinitely with Unable to open RTSP for listening … Address already in use because that ffmpeg version repurposed -timeout into a listen-mode option. Runtime probe now picks -stimeout or -timeout based on what ffmpeg advertises, covering the full pre-deprecation / transitional / modern range without regressing either end. - Camera: P2S RTSP stream no longer drops every frame after the first (#1395, reported by @Tschipel) - Camera: ffmpeg stderr is now captured when an RTSP stream stalls (#1395, reported by @Tschipel) — Previously stderr was only captured when ffmpeg actually crashed, so silent stalls left no diagnostic trace. - Camera diagnostic (stethoscope) is now visible in the pop-out camera window (#1395, reported by @Tschipel) Inventory / scheduler - "Prefer Lowest Remaining Filament" now uses Bambuddy's inventory weight, not just the printer's RFID counter (#1508, reported by @kleinwareio) — Reporter had a cloned spool in slot 1 (950 g) and the original in slot 4 (50 g), the preference enabled, and the dispatch consistently picked slot 1. Root cause: scheduler was reading the printer's tray.remain (which is -1 for non-RFID spools and ties every slot at the sentinel). Now bound-spool slots sort by Bambuddy / Spoolman remaining-grams first; MQTT-only slots fall through to the previous logic. - Scheduler: queue items with force_color_match filament overrides now produce a correct AMS mapping at dispatch (#1437, fixed by external PR #1440 from @Person2099) — When a queue item had force_color_match overrides but no specific plate (plate_id=None), the scheduler was dispatching with ams_mapping: null and the printer was picking AMS slots by filament type only, ignoring the required colour entirely. Contributor fix walks <plate> elements first in filament_requirements.py and falls back to legacy _collect_filaments only when the modern format is absent. - Archive filament colour now reflects the assigned inventory spool, not the slicer's 3MF (#1494, reported by @IndividualGhost1905) — Adding a #000000 black filament was being overridden by the 3MF's embedded colour. - Insufficient-filament pre-print warning now fires on every dispatch path (#1496, reported by @needo37) — Pre-print check was only firing for one of the three dispatch entry points. Spoolman - OpenSpoolman-tagged spools are now selectable in the AMS-slot assignment picker (#1122, reported by @mithkr) — Reporter runs Bambuddy alongside OpenSpoolman; OpenSpoolman writes a different tag shape into spool.extra, which the assignability check was rejecting. Now decided from the slot-assignment ledger instead of the tag field. - Missing-spool-assignment notification no longer false-fires on every Spoolman-mode print (#1473, reported and root-caused by @ojimpo) — Check was only querying one of the two assignment tables. - Per-print weight reporting now works for tag-less spools assigned via the Bambuddy UI (#1459, reported by @Moskito99 — follow-up to #1119) — Postgres + Spoolman reporter saw weight updates skip every non-tag spool assigned through the UI rather than via NFC scan. - AMS hover card and SpoolBuddy fill-bar no longer surface a stale spool after re-assigning a non-RFID slot (#1457, reported by @Menthe11) — P1S reporter with generic non-RFID spools saw the old assignment hang around in both UI surfaces after switching the slot to a different spool. Archives / timelapse - Timelapse now attaches to the archive after a backend restart mid-print (#1485 follow-up, reported by @pwostran) — Companion fix to the duplicate-archive guard from #1485. The same first-push guard that prevents the duplicate also prevented the timelapse-baseline capture, so the post-reboot scan couldn't find new files. Baseline is now captured on the first observed RUNNING state when the start callback is suppressed. - A backend restart mid-print no longer duplicates the job in the archive (#1485, reported by @pwostran) - Timelapse auto-attach works for VP-queue / dispatch prints (#1403 follow-up, reported by @pwostran) — The snapshot-diff strategy that picks the right MP4 was incorrectly attributing files to the wrong archive for dispatched prints. File Manager / Library - "All Files" view now shows files inside subfolders (#1499) — Was previously returning only top-level files. - Library files now display the filename, not the embedded 3MF Title (#1489, reported by @needo37) — File Manager cards / search / sort were keying off file_metadata.print_name, which mismatched what users actually search by. - 3D preview no longer freezes the page on complex multi-part 3MFs (#1412, reported by @anthonyma94) — Opening the 3D preview on a multi-colour parted MakerWorld statue hung the browser tab. - STL thumbnail generation failures now log a full traceback — Surfaced by the #1480 support bundle. Printers / connectivity - File Manager no longer polls the printer over FTPS every 30 seconds while open (#1480, reported by @OscarsWorldTech) — P1S reporter's printer was churning through MQTT disconnect/reconnect cycles every 30s because the File Manager was opening idle FTPS sessions to the printer for no functional reason. - Printer serial numbers normalized on input; stale connection that never receives a status report now logs an actionable hint (#1465, reported by @jmneely94) - Smart-plug "Auto Off after Drying" no longer kills the printer seconds into a drying cycle (#1462, reported by @Kyobinoyo) — X2D reporter set a 1-hour dry cycle and the smart plug cut power 8 seconds in. False "drying complete" detection on power-up state. - AMS drying popover now scrolls instead of clipping the Start button on short viewports (#1458, reported by @kleinweby) - Add Printer no longer hangs the container on P1S (#1445, reported by @psybernoid, confirmed by @thomassjogren) — Regression introduced in 0.2.4.2 by the fix(printers) cleanup. - FTP: P2S upload truncates / 426 "Failure reading network stream" on Python 3.13 (#1401, reported and root-caused by @iitazz) — P2S firmware 01.02.00.00 reporter on Python 3.13 saw uploads truncate at exactly 1.8 MB. SpoolBuddy - Spool ID surfaced everywhere a spool's identity is rendered + Write-Tag page honours Spoolman mode (#1439, reported + partially prototyped by @flom89) Stats - Print Activity heatmap buckets prints by local date, not UTC date (#1446, reported and root-caused by @needo37) — CDT (UTC-5) reporter noticed prints finished in the evening appeared on the wrong calendar day. - Failure Analysis widget no longer shows "Unknown" for archives classified after the fact (#1444, reported and root-caused by @needo37) UI / PWA - PWA: in-app install button + self-host the Inter font (#1460, reported by @Soopahfly) — Reporter could install Bambuddy as a PWA on desktop but not on Android, and the font was being fetched from rsms.me on every page load. - Self-hosted Inter font now actually loads — /fonts/*.woff2 was not served (#1460 follow-up) — The browser console logged downloadable font: rejected by sanitizer because the static mount didn't include the fonts subdirectory. - Cloudflare-fronted Bambuddy no longer needs an unsafe-inline override to load (#1460 follow-up, reported by @Soopahfly) — CSP script-src switched to nonce-based so Cloudflare's auto-injected email-obfuscation / Rocket Loader script passes without the override. - Local Profiles: search bar no longer disappears when a query matches nothing (#1470, reported by @pwostran) - Failure Detection: Status panel Low / High thresholds now reflect the selected sensitivity (#1469, reported by @JohnMacOB) Security - idna >= 3.15 — clears CVE-2026-45409 (ReDoS in idna.encode() with crafted Unicode payloads). - starlette >= 1.0.1 — clears PYSEC-2026-161 (malformed Host header crash in request.url construction). - PyJWT CVE-2025-45768 permanently ignored in pip-audit — Advisory is disputed by the PyJWT maintainers; Bambuddy auto-generates 86-char secrets via secrets.token_urlsafe(64), file-loaded path rejects secrets shorter than 32 chars. --- **Thanks** **@Person2099** ([PR #1440](https://github.com/maziggy/bambuddy/pull/1440) — `force_color_match` AMS-mapping fix). Thanks for the upstream patch. |
||
|
|
02e119ea45 |
fix(test): use /nonexistent/ instead of /tmp/ to satisfy Bandit B108
The test_returns_empty_when_3mf_missing test sets a deliberately
non-existent file_path on a PrintArchive to verify
compute_deficit_for_queue_item handles the missing-3MF branch
gracefully. The path just needs to fail an existence check — the
/tmp/ prefix was incidental.
Bandit B108 ("insecure temp file usage") regex-matches /tmp/,
/var/tmp/, and /dev/shm/. Dropping /tmp/ in favour of /nonexistent/
keeps the test behaviour identical (still a guaranteed-missing
path, still triggers the missing-file branch) while clearing the
GitHub Advanced Security finding on PR #1514 without adding a
# nosec annotation.
|
||
|
|
d64c0dcd08 | Merge remote-tracking branch 'origin/main' into 0.2.4.3 | ||
|
|
07ea69c3f0 |
test(docker): include gcode_viewer/ in the test image so the packaging assertion actually runs
Dockerfile.test only COPYed backend/ and pyproject.toml, so the integration test at tests/integration/test_gcode_viewer.py:63 silently pytest.skip'd in every Docker run with "gcode_viewer/ index.html not present at /app/gcode_viewer/index.html". That was deliberate fallback behaviour for unit-test environments where the assets are intentionally absent, but in CI it meant the #1218 packaging regression (3D Preview returning {"detail":"Not Found"} because the embedded PrettyGCode viewer wasn't bundled into the prod image) had no test guarding against a recurrence — the test that was supposed to catch it was the one being skipped. Add COPY gcode_viewer/ ./gcode_viewer/ to the backend-test stage, matching the path the production Dockerfile uses (static_dir.parent / "gcode_viewer" = /app/gcode_viewer/) so the assertion runs against the same layout the app sees at runtime. Path-anchored comment in the Dockerfile so a future maintainer doesn't strip the COPY as unused. |
||
|
|
f513c2c1db | Bumped version | ||
|
|
a64df5a922 | Updated CHANGELOG | ||
|
|
b9d51ffd80 |
chore(deps): floor-pin starlette>=1.0.1 against PYSEC-2026-161
pip-audit reported starlette 1.0.0 in the dev venv. starlette is transitive via fastapi, whose range still admits 1.0.0, so the resolver was silently picking the vulnerable build. Same floor-pin strategy as the existing idna/urllib3 entries — direct pin in requirements.txt with a why-comment so it isn't mistaken for an unused line and dropped later. Verified clean: pip-audit reports "No known vulnerabilities found" after the upgrade (starlette 1.0.0 → 1.1.0 locally). |
||
|
|
3b9633a178 |
● feat(support): include sanitized connection / VP / log-health diagnostics in support bundle and bug report (#1506 follow-up)
The three diagnostic surfaces shipped earlier this month ( |
||
|
|
cc468ddd42 | Updated BACKERS.md | ||
|
|
17602774f3 | Updated BACKERS.md | ||
|
|
03896d1af0 |
fix(scheduler): use inventory weight for "Prefer Lowest Filament" sort (#1508)
Reporter has a P1S with an inventory spool cloned to slot 1 and the
original (much further used) in slot 4, the preference enabled, and
the dispatch picked slot 1 every time. The sort's been blind to
Bambuddy inventory weights — it reads MQTT `tray.remain`, the
printer firmware's RFID-decremented value, which has two limitations:
- Bambu RFID only. Non-RFID spools report -1 and get clamped to a
sentinel; multiple non-RFID trays then tie in the sort and Python's
stable sort collapses to AMS-slot insertion order, so slot 1 wins.
- Even when set, it's the printer's counter, not Bambuddy's
`label_weight - weight_used` (internal) or Spoolman's
`remaining_weight`. The two diverge whenever the user re-spools,
swaps cardboard, or runs a print outside Bambuddy.
The reporter is on internal-inventory mode with non-RFID spools — both
failure modes apply, hence slot 1 every time.
Fix: when a slot is bound to an inventory spool the inventory record's
remaining weight becomes the sort signal. New async helper
`_build_inventory_remain_overrides(db, printer_id, loaded)` returns
`{global_tray_id: remaining_grams}` for bound slots — internal mode
joins SpoolAssignment → Spool once per dispatch; Spoolman mode joins
SpoolmanSlotAssignment then reuses `_spoolman_remaining_grams` from
filament_deficit.py for parity.
New `_prefer_lowest_sort_key` does a two-tier comparison: inventory-
tracked spools sort BEFORE MQTT-only spools, then ascending by
remaining within each tier, then ascending by ams_id*4+tray_id as the
deterministic slot tie-breaker. The tier flag dominates so grams
(inventory) and percent (MQTT) never get cross-compared — no unit
conversion needed.
MQTT-only behaviour is preserved exactly: remain=-1 still maps to the
101 sentinel and slot order still decides on ties. Users who haven't
bound any inventory spool see no change. The DB lookup runs only when
prefer_lowest_filament is enabled.
External / VT slots are skipped (tracked separately from AMS bindings).
|
||
|
|
eae96da56e |
fix(camera): probe ffmpeg for the right RTSP socket-timeout flag (#1504)
A previous attempt swapped `-timeout` → `-stimeout` unconditionally to
fix EADDRINUSE on the reporter's transitional ffmpeg. That broke every
install on a modern ffmpeg (5+/6+/7+) — current Debian/Ubuntu/Homebrew
— where `-stimeout` was removed and `-timeout` is back to meaning
socket I/O. Verified locally: `ffmpeg -stimeout ...` errors
"Unrecognized option 'stimeout'" on ffmpeg 7.1.
install on a modern ffmpeg (5+/6+/7+) — current Debian/Ubuntu/Homebrew
— where `-stimeout` was removed and `-timeout` is back to meaning
socket I/O. Verified locally: `ffmpeg -stimeout ...` errors
"Unrecognized option 'stimeout'" on ffmpeg 7.1.
ffmpeg has shipped THREE arrangements of this option over time and
Bambuddy supports the full range:
- Pre-deprecation (early 4.x and earlier): `-timeout` is socket I/O.
- Transitional (~late-4.x, Jammy-era): `-timeout` is deprecated and
repurposed to RTSP listen-mode timeout; any non-zero value implies
`-listen`, which makes ffmpeg bind the TLS-proxy port and fail with
EADDRINUSE. `-stimeout` is the replacement socket I/O option.
- Modern (5.x / 6.x / 7.x): `-stimeout` REMOVED. `-timeout` is back to
socket I/O — the original meaning.
So no single literal is correct on all installs.
Fix: `rtsp_socket_timeout_flag()` in services/camera.py probes
`ffmpeg -h demuxer=rtsp` once and picks `-stimeout` when ffmpeg
advertises it (covers transitional + older builds that kept it as an
alias), else `-timeout` (modern + pre-deprecation). Cached at module
level for the process lifetime — ffmpeg doesn't swap mid-run.
The function returns the option name without a leading dash; callers
prepend it themselves so a formatting bug can't pass an empty flag.
Wired into both RTSP ffmpeg call sites in lockstep: routes/camera.py
(printer camera) and services/external_camera.py (external RTSP),
which use the same TLS-proxy + ffmpeg pattern and would hit the same
regression on either ffmpeg cohort.
Tests: 8 in test_ffmpeg_rtsp_timeout_flag.py — 6 probe unit tests
(prefers stimeout when advertised, falls back to timeout on modern,
defaults to timeout when ffmpeg missing or probe raises, caches across
calls, trailing-space substring guard against `-listen_timeout`
false-positives), 2 parametrised guards against either RTSP ffmpeg
argv re-hard-coding a literal instead of consuming the probe. 37
probe + existing external-camera tests green.
|
||
|
|
6591fc011f |
fix(slicer): filter process / filament presets by nozzle diameter too (#1325 follow-up #2)
After the @BBL name fallback landed, IndividualGhost1905 reported that
an X2D 0.4 selection still mixed 0.2 / 0.6 / 0.8 nozzle process variants
into the main dropdown. The fallback's two extractors —
extractPrinterPresetModel and extractBblToken — both ended their regex
with `\s+[\d.]+\s*nozzle\s*$` and discarded the match, reducing
"Bambu Lab X2D 0.4 nozzle" and "0.40mm Strength @BBL X2D 0.8 nozzle"
to the same "X2D" string. Match was model-only; nozzle ignored.
Bambu's naming convention: 0.4 is the default and DROPS the suffix; 0.2
/ 0.6 / 0.8 carry an explicit "<size> nozzle" segment. So a process
preset with no suffix is implicitly 0.4 — not "any nozzle".
Have both extractors return { model, nozzle }, parsing the suffix out
instead of stripping. classifyByBambuName then requires both model AND
nozzle to compare equal; a null process nozzle counts as "0.4" per the
convention above. Differing nozzles fall into the existing "Other
printers" group — no new group label.
The bundle path was already nozzle-correct: a .bbscfg is scoped to one
printer-preset-name including its nozzle, and the bundle-side exact
match is therefore nozzle-aware. Only the @BBL name fallback needed
fixing. The `compatible_printers` tier is also unaffected (Bambu's
bundled `compatible_printers` lists include the full printer-preset
name with nozzle, so disambiguation already works).
If the selected printer preset name has no parseable nozzle (non-Bambu
/ hand-typed), the matcher degrades to model-only. Bambu printer
presets always carry one in practice; this is defensive.
9 new tests cover the matrix:
- 0.4 printer ↔ no-suffix process: match
- 0.4 printer ↔ 0.6 / 0.8 process: mismatch
- 0.6 printer ↔ 0.6 process: match
- 0.6 printer ↔ no-suffix process (=0.4): mismatch
- same rule applied to filament presets
- explicit "0.4 nozzle" suffix on process still matches 0.4 printer
- wrong-model still mismatches even when nozzles agree
- no-nozzle printer name degrades to model-only match
One existing test ("handles a trailing nozzle-size suffix on the @BBL
tag") had asserted that a 0.6-nozzle process matched a 0.4 printer —
the exact reporter complaint. Reframed: matching 0.4-suffix still
matches, 0.6-suffix now mismatches.
|
||
|
|
ed08ed3787 |
fix(timelapse): capture baseline on restart-recovery so post-reboot timelapses attach (follow up issue #1485)
When Bambuddy is restarted mid-print, the first MQTT push from the printer carries `_previous_gcode_state = None`. The #1304 guard deliberately suppresses on_print_start on that first push to prevent duplicate archive creation — but on_print_start is also where _capture_timelapse_baseline_at_start runs, so the in-memory _timelapse_baselines dict stays empty for the resumed session. At PRINT COMPLETE, _scan_for_timelapse_with_retries finds no baseline and falls into its "take baseline now" fallback. By that point the printer has already uploaded the in-flight MP4, so the snapshot includes the new file. Every "Found N files / no new files since baseline" retry then fails to detect a diff, and the archive ends up with no timelapse attached — pwostran's report (#1485 follow-up): card shows the finish snapshot but no video. Add a sibling callback on_print_running_observed that bambu_mqtt fires in the "Now tracking RUNNING state" branch when on_print_start was suppressed. main.py wires it to a thin handler that looks up the printer row and calls the existing _capture_timelapse_baseline_at_start. Idempotent — skips if a baseline already exists (handles the rare same-session race where on_print_start also fires for some reason). The printer doesn't upload the timelapse until after PRINT COMPLETE, so a baseline captured any time during the print is still pre-upload — no narrow window to hit. Verified against the in-the-field logs in #1485 (pwostran's 2026-05-23 support bundle): pre-reboot: baseline = 7 files reboot post-reboot completion: fallback baseline = 8 files (includes new MP4) -> all 4 retry attempts report "no new files since baseline" 10 new tests cover both the MQTT-side fire decision (fires when suppressed, doesn't fire when on_print_start handles it, once per session, payload shape mirrors on_print_start) and the main.py handler (snapshot capture, double-capture guard, missing-printer-row guard). |
||
|
|
c0c07bc509 |
fix(archives): cross-model re-slice no longer carries source printer_id
When the user re-sliced an H2D archive for X1C, the new archive card and
reprint modal both showed the source printer's name (e.g. "Workshop H2C")
even though sliced_for_model on the same row correctly read "X1C".
slice_and_persist_as_archive copied source_archive.printer_id verbatim
onto the new PrintArchive row. Both the archive card
(ArchivesPage.tsx:3571) and the reprint modal read printer_id first and
only fall back to sliced_for_model when it's None. With printer_id set
to the source's H2D printer, neither could see that the new file is for
a different model.
Same shape as the sliced_for_model fix already in the same function — a
cross-model re-slice means the source's physical printer isn't where
this output prints anymore. When the slicer-baked target model differs
from source_archive.sliced_for_model, drop printer_id so both surfaces
fall back to the sliced_for_model badge ("X1C"). Same-model re-slices
keep printer_id so the reprint modal still pre-selects the source
printer.
Edge case: when source_archive.sliced_for_model is None (older archives
that predate that column being populated), we can't tell whether this
is a cross-model re-slice. Fail open and preserve printer_id rather
than spuriously nulling it.
slice_and_persist (the library-file path) doesn't have this bug —
LibraryFile has no printer_id column.
Tests in TestSliceArchiveResliceModel cover all three branches: cross-
model nulls printer_id; same-model keeps it; unknown source model
preserves it.
Surfaced via the bambuddy demo doing H2D -> X1C re-slices after the
.bbscfg System-tier slicing fix landed.
|
||
|
|
5f473801f8 |
fix(file-manager): list-view actions clipped + preview modal slice button now respects Slicer API setting
Two small bugs reported in chat:
1. List-view actions column was clipped off the right edge. The grid
template fixed the actions column at 80px - a sliced 3MF row renders
7 icon buttons (Print, Schedule, Slice, 3D Preview, Download, Rename,
Delete) which need ~220px. The wrapper's overflow-hidden (there for
rounded corners) then swallowed everything past column 80px.
Switched the trailing column from 80px to min-content so it sizes to
the widest action strip across all rows, and replaced overflow-hidden
with overflow-x-auto so narrow viewports get a scrollbar instead of
vanished icons. Rounded corners survive because the border-radius is
on the wrapper itself, not on the overflow box.
2. The 3MF preview modal's "Open in Slicer" button always launched the
external slicer (BambuStudio / Orca) regardless of Settings -> Workflow
-> Slicer -> Use Slicer API. With the API enabled, the file-row Cog
already opens the in-app Bambuddy SliceModal - the preview-modal
button should match.
Added an optional onSliceWithBambuddy callback to ModelViewerModal.
When set AND settings.use_slicer_api is true AND we're in library mode
on a sliceable type (.3mf/.stl/.step/.stp), the header button becomes
a Cog labelled "Slice" and calls the in-app handler. Otherwise it
falls back to the existing ExternalLink "Open in Slicer" button.
FileManagerPage wires the callback to close the viewer and open the
SliceModal with the same file, gated on the same isSliceableFilename
+ library:upload permission checks the row's Cog already uses. No new
i18n keys - reuses slice.action ("Slice") that the file row already
ships in all 9 locales.
|
||
|
|
3058c5789b |
fix(slice): @BBL name fallback for users without slicer bundles (#1325 follow-up)
The first cut of #1325 swapped a stale hardcoded model table for bundle-based compatibility matching: a cloud / standard process or filament preset was classified by consulting the user's uploaded Slicer Bundles (.bbscfg). That works perfectly when bundles cover every printer in the user's cloud catalogue, and silently no-ops otherwise - every cloud preset resolves to 'unknown', nothing moves into "Other printers", and the dropdowns look identical to the pre-fix state. The reporter saw exactly this on a clean install with no bundles uploaded. Restored BambuStudio's `@BBL <token>` name convention as a third tier below the bundle path, but driven by the canonical backend PRINTER_MODEL_MAP - exposed via a new GET /slicer/printer-models route - rather than a manually-maintained frontend table. The matcher inverts the registry into short-code -> printer-fragment ("X1C" -> "X1 Carbon"), normalises whitespace + case so "A1 mini" and "A1 Mini" compare equal, and falls back to raw-token compare for models not yet in the registry (so a future "Q1" matches without a code change). Adding a new Bambu model still touches exactly the one backend file already listed in the Bambu Model Codes registry. Tests: 2 new in test_slicer_presets.py (route returns the full PRINTER_MODEL_MAP, route returns a copy not the live dict); 11 new in slicerPrinterMatch.test.ts covering registry-driven X1C vs X1 Carbon, A1 vs A1 mini, H2D vs H2D Pro, P2S / H2C / H2S / X2D (which the original hardcoded list was missing), raw-token fallback, registry-not-loaded-yet degradation, and the precedence rules between compatible_printers / bundle / @BBL name. 38 slicer-presets + 36 slicerPrinterMatch + 34 SliceModal tests green; backend ruff clean; frontend build clean. |
||
|
|
4096d8d6bd |
fix(csp): nonce-based script-src so Cloudflare-injected scripts pass (#1460 follow-up)
Behind Cloudflare, the bot-detection script CF injects into every HTML response carries a hash that rotates per request, so it can never be allowlisted by hash. Reporters with CF in front had to relax their NPM CSP to 'unsafe-inline' as a workaround. Per Cloudflare's documented behaviour, when a nonce is present in the page's script-src, CF clones it onto its injected <script>. The SPA CSP now stamps a fresh per-request nonce via secrets.token_urlsafe(16), keeping 'self' for our own scripts (index.html has had no inline scripts since the SW registration moved to /sw-register.js in the original #1460 PR), so no HTML body rewriting is needed. Also folded in: /manifest.json, /sw.js and /sw-register.js now accept HEAD as well as GET, so `curl -I` and uptime scanners stop returning 405 on those routes - a separate red herring during this issue's debugging. Tests: 3 new in test_security_headers.py - 'nonce-' token stamped into SPA script-src while 'self' remains and 'unsafe-inline' does not; nonce is fresh per request across 5 sequential calls; HEAD on the three PWA routes never returns 405. 22/22 security-header tests green; backend ruff clean. |
||
|
|
4686d108ef |
feat(slice): cross-printer re-slicing across nozzle classes + multi-plate slice-all
Re-slicing a 3MF authored for a single-nozzle printer (X1C, P1S, A1, P2S)
onto a dual-nozzle printer (H2D / H2D Pro) — or vice versa — previously
failed with "G-code in unprintable area of multi-extruder printers" (the
source's bed-coordinate layout lands in the H2D's per-nozzle dead zone)
or, on multi-color projects, a hard SIGSEGV inside the slicer's ZFiller
polygon-clipping. Earlier shipped a fail-fast 400 guard; this drop lifts
it and actually does the conversion by forwarding the sidecar's existing
--arrange flag when the source and target nozzle classes differ. BS
itself reconciles the embedded project_settings.config against the new
printer that way, the same way the GUI's "Switch Printer" operation
does. The guard becomes a kept-for-compat no-op.
Slice-all-plates added to the SliceModal: a checkbox for multi-plate
sources sends plate=0 to the backend, which forwards --slice 0 to the
BS CLI. Same-class slice-all produces one multi-plate output 3MF in a
single sidecar call. Cross-class slice-all loops per plate (BS's
--arrange is project-wide and would otherwise consolidate every plate's
objects onto one bed) and merges the per-plate outputs into one
multi-plate 3MF locally via the new merge_plate_3mfs helper. The toast
shows "Plate 2 of 5 — Generating G-code (47%)" through the loop.
Three side fixes surfaced during testing:
- substitute_unused_plate_filaments overwrites unused-slot filaments
with the slot-1 selection before slicing so BS's loaded-filament
temperature validator doesn't reject a PLA print whose unused slot 2
defaulted to ABS in the dropdown
- re-sliced archive thumbnail now prefers the source's per-plate
render (Metadata/plate_N.png) over the project-wide MakerWorld cover
art, because BS CLI with --arrange skips writing a fresh per-plate
preview
- re-sliced archive bed_type now lifts from the sliced output's
curr_bed_type onto the PrintArchive column the card actually reads
Schema: SliceRequest.plate range relaxed from ge=1 to ge=0 to admit
the "all plates" sentinel; SlicerApiService.slice_with_profiles /
slice_with_bundle take an `arrange` parameter.
Tests: 26 in test_slicer_3mf_convert (count / merge / substitute /
extract), 3 in test_slicer_api (arrange wire format), 9 in
test_library_slice_api (guard no-op, bed_type lift, thumbnail
fallback, new cross-class slice-all loop integration test), 2 in
test_archive_service (Auxiliaries fallback), 4 in SliceModal.test
(plate=0 toggle), 2 in SliceJobTrackerContext.test (multi-plate toast
prefix). 659 backend + 42 frontend green; backend ruff clean,
frontend build clean, i18n parity green at 4984 keys × 9 locales.
|
||
|
|
ebba1385d3 |
feat(spoolbuddy-settings): show CPU load on the device card
The SpoolBuddy daemon already reports load_avg (1/5/15 min) and cpu_count in its heartbeat, but the Settings -> SpoolBuddy card only showed CPU temp / memory / disk / system uptime. Adds a fifth tile rendering the 1-min load with core count and percent-of-cores -- e.g. "1.20 / 4 (30%)" -- next to the existing CPU temp tile. Falls back to a bare load number when cpu_count is missing and hides the tile when the daemon doesn't emit load_avg. settings.spoolbuddy.cpuLoad translated across all 9 locales. |
||
|
|
7ea4410b21 |
fix(queue): insufficient-filament warning now fires on every dispatch path (#1496)
The pre-print deficit warning from #720 only ran inside the PrintModal submit flow. Both the green ▶ button on a staged queue row (POST /queue/{id}/start) and the Virtual Printer queue-mode intake bypassed it — auto_dispatch=True VP intakes would dispatch unsupervised onto spools that physically can't complete the print. Extracted the deficit check into backend/app/services/filament_deficit.py (single source of truth, both internal inventory and Spoolman modes). POST /queue/{id}/start returns 409 with a structured deficit payload unless ?skip_filament_check=true. The dispatch scheduler runs the same check before each _start_print; a deficit promotes the item to manual_start + sets a new filament_short flag (idempotent migration on print_queue). The flag clears automatically on the next tick when the operator swaps a spool to one with enough material. Frontend ▶ catches the 409 and opens a confirm modal showing each shorted slot's required vs remaining grams; the row now renders a yellow "Insufficient filament" badge when filament_short is set. Translated across all 9 locales. |
||
|
|
32fcd85827 |
fix(library): "All Files" view now shows files inside subfolders (#1499)
The File Manager sidebar's "All Files" entry was passing include_root=true to GET /library/files because the boolean was derived from `selectedFolderId === null`. The backend's include_root flag means *root files only* when folder_id is null, so a library where every file lived in a subfolder rendered as empty. Pass include_root=false from "All Files" so the backend returns every active file across folders. Adds a regression test that mounts the page, mocks the endpoint to distinguish include_root=true vs false, and asserts both a root file and a nested file appear. |
||
|
|
6d673d0626 | Updated BACKERS | ||
|
|
6b87cdefd7 | Updated BACKERS | ||
|
|
e222a0ef0e |
feat(system): log-health scanner + Add/Edit-Printer setup pre-flight
Adds a passive log-health check that complements the active Connection Diagnostic. Scans Bambuddy's recent app log against a curated allowlist catalog of known failure signatures (rejected access code, FTPS :990 timeout, FTPS TLS failure, flapping MQTT, unreachable camera, SQLite "database is locked" contention), dedupes and classifies each finding as layer8/environment/bug, and deep-links to the troubleshooting wiki. Sample log lines are sanitized before they leave the process. Exposed via GET /system/health and surfaced on two surfaces sharing one SystemHealthPanel component: a System Health section on the System page, and inline in the bug reporter when the form opens. The Add-Printer and Edit-Printer dialogs gained a setup-time pre-flight: saving runs the connection diagnostic and, on a failed check, warns with a "save anyway" escape hatch instead of silently saving a printer that will immediately show offline. Log read/parse/sanitize primitives extracted from routes/support.py into a shared services/log_reader.py (behaviour-preserving); affected support tests repointed accordingly. Tests: test_log_health.py (11), test_system_api.py (2 new), SystemHealthPanel + BugReportBubble + AddPrinterPreflight + EditPrinterPreflight (8 frontend). All strings translated across the 9 locales. Backend ruff clean, full unit suite green, frontend build + eslint clean, i18n parity green. |
||
|
|
ab8e07618f |
fix(inventory): archive filament colour follows the assigned spool, not the 3MF (#1494)
An archive's filament_color was parsed verbatim from the print job's 3MF (filament_colour in project_settings.config) — the slicer's filament-slot colour, which a user picks independently of the exact hex they curate on the Bambuddy inventory spool. So a print from a #000000 inventory spool showed #161616 (the slicer's near-black) in the archive card and the Color Distribution graph, even though usage tracking correctly decremented the right spool. Once usage tracking has resolved the print's filament slots to inventory spools, the spool colours are authoritative. _track_from_3mf (built-in inventory) and report_usage (Spoolman mode) now overwrite the archive's filament_color with the slot-ordered, de-duplicated colours of the matched spools. The rewrite is all-or-nothing: it only applies when every used slot resolved to a spool carrying a colour, so a partially-mapped multi-colour print keeps the 3MF colour rather than silently dropping the unmatched slots. Shipped for both inventory modes: built-in spools read Spool.rgba, Spoolman spools read the spool's filament.color_hex (fetched via get_spool for tag-less slot-assignment matches). New helpers _spool_color_to_hex / _archive_colors_from_spools in usage_tracker.py, reused by spoolman_tracking.py via _apply_spool_colors_to_archive. Tests: 12 new in test_usage_tracker.py (hex normalisation, the all-or-nothing rule across single/multi/partial/no-colour/AMS-fallback cases, end-to-end rewrite), 4 in test_spoolman_tracking.py (Spoolman rewrite + empty/partial/missing-archive no-ops). 70 tracking tests green; backend ruff clean. |
||
|
|
6d7a92c024 |
fix(camera): P2S RTSP stream dropped every frame after the first (#1395)
A fresh P2S support bundle showed ffmpeg's reason for the stall: `frame=1 time=00:00:00.06 dup=0 drop=526 speed=0.0037x`. ffmpeg connects and frames arrive (drop counter climbs ~15/s), but it emits one output frame and the output clock freezes. The streaming command ends with `-r 15`, putting ffmpeg in CFR mode: it drops/dupes input frames to hit 15 fps based on the source's timestamps. P2S firmware 01.02.00.00 sends an RTSP stream whose RTP timestamps don't advance, so CFR treats every frame after the first as a same-timestamp duplicate and drops it. Snapshot capture works on the same printer because that path has no `-r` (no CFR conversion); X1/H2 are unaffected because their firmware timestamps are correct. The earlier probesize fix was masking this second bug. Add `-use_wallclock_as_timestamps 1` to the P2S camera profile via the existing extra_ffmpeg_input_args hook. ffmpeg rebuilds each packet's PTS from arrival wall-clock time, the output clock advances, and CFR conversion works. No dataclass change, no other model touched. Tests: 2 new in test_camera_profiles.py (P2S splices the flag+value pair; default profile keeps extra_ffmpeg_input_args empty so the override never leaks to X1/H2). |