Adds bambuddy-windows-x64-setup.exe (unversioned) alongside the
date-stamped bambuddy-<version>-windows-x64-setup.exe for stable
and beta tag releases. Lets external surfaces (website, wiki,
newsletters) link to a stable URL that survives version bumps:
https://github.com/maziggy/bambuddy/releases/latest/download/
bambuddy-windows-x64-setup.exe
Daily prereleases are excluded — GitHub's `latest` redirect skips
prereleases so the alias would add no value there, and an
unversioned name next to a date-stamped versioned one on a daily
release page is semantically confusing.
Address CodeQL actions/missing-workflow-permissions finding. Least-
privilege at workflow level: contents: write is required by
softprops/action-gh-release to attach the installer .exe to a tag
release; all other steps are read-only.
windows-latest runners ship Inno Setup 6.7.1 pre-installed under the
same path we already hardcode for ISCC.exe; the choco install was trying
to downgrade to 6.2.2 and failing on the version mismatch.
Lays down the Inno Setup + embedded Python pipeline for producing a
self-contained Bambuddy Windows installer .exe. The installer ships
an embedded Python 3.13, the pre-built React bundle, NSSM (service
supervisor) and ffmpeg — no host Python or Node required on the
target machine.
Architecture:
- Install: C:\Program Files\Bambuddy (admin install, one-time UAC)
- Data: C:\ProgramData\Bambuddy\data (preserved on uninstall)
- Service: registered via NSSM, runs as LocalSystem, autostart on boot
- UI: browser at http://localhost:8000 (Start Menu shortcut)
Files:
- installers/windows/build.py stages embedded Python + deps,
frontend bundle, NSSM, ffmpeg
- installers/windows/bambuddy.iss Inno Setup compiler script
- installers/windows/service/*.bat NSSM register/deregister
- .github/workflows/windows-installer.yml CI build on tag push + manual
dispatch, uploads .exe artifact
build.py hard-fails on non-Windows hosts; Wine cross-build is an
unsupported escape hatch behind --allow-non-windows. v1 ships unsigned
(SmartScreen warns on first run) — production signing will be wired up
via SignPath OSS once the application is approved.
See installers/windows/README.md for build prerequisites and the
embedded-Python ._pth gotchas.
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.
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).
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.
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).
+ 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.
A triage review of the last 200 closed issues found ~1/3 were
user-side setup errors — printer not in LAN developer mode, blocked
ports, Docker bridge networking, wrong access code, cross-subnet —
each costing a multi-round-trip support exchange.
Add a Connection Diagnostic that runs those checks automatically:
- backend/app/services/printer_diagnostic.py: TCP probes of MQTT
8883 / FTPS 990 / RTSPS 322, LAN developer mode, Docker network
mode, printer/host subnet match, MQTT credential class; each
check returns pass/fail/warn/skip with a localized fix.
- Routes: GET /printers/{id}/diagnostic (saved printer) and
POST /printers/diagnostic (pre-save Add-Printer flow).
- ConnectionDiagnostic.tsx: modal + shared checklist, surfaced from
the printer card actions menu, an offline-printer quick button,
the Add-Printer dialog, and a new System-page section.
- The in-app bug reporter scans configured printers when the form
opens and always shows the result inline — a healthy confirmation,
or the detected problem and its fix.
- config.yml troubleshooting link repointed to the rendered wiki
page; bug_report.yml gains a diagnostic checkbox.
Diagnostic strings translated across all 8 locales. Backend service
unit tests (15) + frontend modal tests (3). Ruff clean, frontend
build clean, i18n parity green.
security.yml had this ignore added in 9d440beb but ci.yml runs its
own pip-audit step with a separate ignore list. CI was still failing
on main + dev. Reasoning identical to the security.yml comment —
disputed by PyJWT maintainers, no fix exists, Bambuddy uses
secrets.token_urlsafe(64) and rejects short secrets.
- requirements.txt: pin idna>=3.15 to clear ReDoS in idna.encode() on
crafted Unicode payloads. Transitive via anyio/httpx/requests/yarl,
so the explicit floor stops a future downstream loosening from
silently downgrading us.
- security.yml: permanently --ignore-vuln CVE-2025-45768 (PyJWT). The
advisory is disputed by the maintainers — "key length is chosen by
the application" — and no fix version exists. Bambuddy is safe:
auto-generates secrets via secrets.token_urlsafe(64) and rejects
file-loaded secrets shorter than 32 chars (auth.py:177, :184).
- security.yml: drop the stale Pygments --ignore-vuln CVE-2026-4539.
Pygments has been patched upstream; the ignore no longer matches
anything.
CI only installed requirements.txt, missing pyOpenSSL from
requirements-dev.txt. This caused an ImportError on
TLS_FTPHandler during test collection, blocking all
unit/services tests. Also adds pytest-timeout to dev deps
instead of ad-hoc pip install in CI.