The in-app updater ran `git fetch origin main && git reset --hard
origin/main` regardless of which version the GitHub releases API
reported as latest. So whenever the latest release lived on a branch
other than main — e.g. during a beta cycle when 0.2.4b1 sits on its
own branch and main still points at the previous stable — clicking
Apply Update appeared to succeed but the user actually stayed pinned
to old main HEAD.
Fix: extract `_discover_target_release(db)` mirroring the same
release-API + include_beta_updates selection the GUI's update-check
already uses, pass the resolved tag (e.g. `v0.2.4b1`) into
`_perform_update(target_ref)`, and run `git fetch --prune --tags
origin && git reset --hard <target_ref>`. The fetch now pulls --tags
so a tag ref is locally resolvable; the reset takes the caller's
ref instead of a hardcoded branch. apply_update now returns a clear
error if no release resolves, instead of silently kicking off an
update that can't land.
The in-app Apply Update path unconditionally ran `git remote set-url
origin https://github.com/maziggy/bambuddy.git` before fetching, on
the theory that systemd service users wouldn't have SSH keys. True
in production, but it also clobbered every developer's SSH origin
the moment they tested the upgrade flow against their own checkout.
Next `git push` then prompted for HTTPS credentials and bounced.
New behaviour: read `origin` first via `git remote get-url`, parse
out the (owner, repo) pair using a small helper that handles all
four canonical forms (git@github.com:owner/repo[.git] and
https://github.com/owner/repo[.git]), and only rewrite if it doesn't
already resolve to maziggy/bambuddy. Native installs with no remote
or pointing at a fork still get reset to the canonical HTTPS URL.
Three new regression tests in test_updates_api.py:
- parser accepts SSH/HTTPS, with/without .git, rejects non-GitHub
- SSH origin pointing at maziggy/bambuddy is preserved (the
developer-footgun case)
- origin pointing at a fork still gets rewritten to HTTPS (the
original behaviour we don't want to lose)
Native-install upgrade via the in-app Apply Update button got the new
code in via `git reset --hard origin/main` but then logged
ERROR: Could not open requirements file:
[Errno 2] No such file or directory: 'requirements.txt'
and continued. The new deps never installed, leaving the user with
new code but stale dependencies — surfaces as cryptic import errors
on the next restart.
Root cause: `pip install -r requirements.txt` ran with
`cwd=settings.base_dir`. On a native install, systemd sets
DATA_DIR=$INSTALL_PATH/data so base_dir resolves to the data dir
(e.g. /opt/bambuddy/data), not the source tree. Pip doesn't walk up
looking for the requirements file the way git walks up looking for
.git, so it fails. Same bug affected the optional npm step
(`frontend_dir = base_dir / "frontend"` doesn't exist).
Fix: introduce `settings.app_dir` pointing at the source-tree root
(distinct from `base_dir` only on native installs) and run pip +
npm with `cwd=settings.app_dir`. Git ops keep using `base_dir`
because they already work (git walks up).
Docker users were unaffected — Docker doesn't use the in-app updater
(image pull replaces it).
Regression test in test_updates_api.py mocks every subprocess in
_perform_update, captures their cwd, and asserts the pip step runs
in app_dir and that requirements.txt actually exists there. Any
future refactor that re-introduces cwd=base_dir for the pip step
fails CI before another user trips over it.
The _is_docker_environment() fallback assumed Docker when .git/ was
absent, which is also true for native installs in Proxmox LXC
containers. Replace the .git/ fallback with a check of
/run/systemd/container (only matches docker/podman/oci, not lxc).
Daily beta build tags (e.g. v0.2.3b1-daily.20260316) were not detected
as prereleases because parse_version() only checked the last
dot-separated segment for letters. The daily date suffix is purely
numeric, so it passed the stable release check. Now checks the entire
version string for prerelease markers.
parse_version() misclassified "0.2.2b4-daily.20260313" as a release
because the daily suffix made the last dot-segment ("20260313") contain
no alpha chars, bypassing prerelease detection. Strip -daily.YYYYMMDD
suffix before parsing so daily builds compare as their base beta version.
Users on the Docker `latest` tag were seeing update notifications for beta
releases (e.g. v0.2.1b) they couldn't install. The update checker now fetches
/releases instead of /releases/latest and filters by parse_version() prerelease
detection. A new toggle in Settings (default: off) lets users opt in to beta
notifications.
Backend /updates/check endpoint now returns early without calling
GitHub API when check_updates is disabled. Settings page no longer
auto-fetches update status when the setting is off. Printer card
firmware badge falls back to showing the current version from MQTT
instead of disappearing when firmware update checks are disabled.
The CodeQL cleanup in "Housekeeping" (2b11efd) bulk-narrowed except
clauses across 50+ files, breaking FTP uploads on ALL printer models.
ftplib.error_perm (550 errors) is not a subclass of ftplib.error_reply,
so diagnose_storage() CWD failures escaped the handler and prevented
STOR from ever executing — causing 100% upload failure and HTTP 500s
on /api/v1/archives/{id}/reprint and /api/v1/library/files/{id}/print.
FTP fixes:
- Remove diagnose_storage() from upload hot path
- Change all except (OSError, ftplib.error_reply) to
except (OSError, ftplib.Error) across bambu_ftp.py
Exception handling reverts (9 files):
- Revert narrowed except clauses back to except Exception in route
handlers and service code where broad catches are intentional
defensive programming (archive parsing, HTTP clients, 3MF/ZIP
processing, Home Assistant, firmware checks)
- Keep narrow exceptions only where safe (single-op blocks like
int(), file.unlink(), socket.close())
- Remove unused XMLParseError imports from archive.py, threemf_tools.py
Version system:
- Add 4-segment version support (e.g. 0.1.8.1) for patch releases
- Bump version to 0.1.8.1
Closes#287