mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
dev
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
12dddada0a | File Manager: external link, notes and photos on library files (#3128) | ||
|
|
02616f0c91 |
fix(queue): stop a library-file delete from destroying the jobs queued against it (#2819)
Nothing tied a library file to the queue rows pointing at it, and the FK that describes the relationship is ON DELETE CASCADE -- which SQLite does not enforce and PostgreSQL does. So the same fault had two faces: rows left pointing at a file that no longer existed, failing at the printer with "Library file not found" days later, or rows deleted outright with no error and no history. Two routes into it, both fixed by taking the queue off the file before the row goes. Dispatch (the reported case): quantity>1 on the printer-card upload-and-print flow puts cleanup_library_after_dispatch on every copy, and _clone_queue_item copies library_file_id onto batch clones, so the first dispatch consumed the file the rest were waiting on. The copies are now pointed at the archive that dispatch just created -- it holds its own copy of the 3MF -- and the consume flag is cleared on them. A copy already printing from its own archive keeps it, a finished one keeps its outcome, and a cross-model item (#671) keeps any candidate this does not consume. Deletion: the File Manager, bulk delete, folder delete, emptying the trash and the retention sweeper all removed rows with queued work against them. Folder delete did not even clear the cross-model candidates, because the file-id walk it already performs threw its result away. Jobs waiting on a deleted file are now cancelled at that moment, naming the file, and every other row referring to it is detached rather than destroyed -- print history and batch progress are counted from those rows. A job that is printing is left alone: what is deleted is the library copy, not the copy on the machine. The trash is reversible so it still changes nothing about the queue, and a job dispatched while its file is in the trash now says so instead of "not found". Verified row for row on PostgreSQL 16 as well as SQLite: without this, PostgreSQL deletes every queue row referencing the file. |
||
|
|
a9b57ccd3c |
Add variant-group endpoints and cross-model queue creation (#671, #2570)
Adds /library/variant-groups for declaring that several sliced files are the same job for different printers, and a variants payload on queue creation that turns such a set into one queue item with a candidate per file. The candidate set is validated as a set: one file per printer model, each file sliced for the model it is offered as, and at least one model with an active printer. A cross-model item deliberately holds no file of its own, because print_queue.library_file_id is ON DELETE CASCADE and would destroy the whole job when a single alternative is deleted. Fixes internal printer-model codes never being resolved on queue create and update: normalize_printer_model returns unknown input unchanged, so the or-chain never reached the code map and a "C13" target matched no printer and waited forever. Skips candidates whose file is trashed or missing. Library deletes are soft, and SQLite runs with PRAGMA foreign_keys off, so neither case is covered by the schema; the hard-delete paths now also drop the rows. Adds library_files.variant_target_model so a user can say which printer a file without slicer metadata is for, kept out of file_metadata so the assertion is never mistaken for parsed data. |
||
|
|
396e9aa09e |
security: harden path-traversal class across routes + services; fifth CI backstop
Two attacker-controlled strings were being joined to library_dir with no
resolve + containment check in the project ZIP import endpoint:
- linked_folders[*].name from the request's project.json
- per-entry zf.namelist() paths from the ZIP itself
An absolute path in either field collapsed the join (Path("/lib") / "/etc"
becomes Path("/etc") because pathlib discards the left side when the right
is absolute) and the next write_bytes landed wherever the attacker chose.
Adjacent finding from the routes audit: GET /archives/{id}/photos/{filename}
had NO validation on filename and FileResponse-served arbitrary paths -
the DELETE counterpart at least gated on the photos membership check.
Adjacent finding from the services audit: ArchiveService.attach_timelapse
wrote archive_dir / filename where filename ultimately came from a printer's
FTP listing (compromised-printer threat model) or the /timelapse/select
query param. A malicious printer that exposes a directory entry with ..
segments could write the timelapse outside the archive directory.
New backend/app/utils/safe_path.py::safe_join_under(parent, *parts) is the
single source of truth: rejects empty / null-byte / absolute parts up-front,
joins under parent, resolves both sides, asserts is_relative_to. Returns the
resolved canonical path on success, raises HTTPException(400) on escape, or
PathTraversalError when http=False (for service-layer callers that need to
match a non-HTTP return contract).
Wired into the import vectors, both archive photo handlers, and the
attach_timelapse service. The full audit sweep inspected every Path/Name
join in backend/app/api/routes/ AND backend/app/services/ - 25 route-layer
sites + 8 service-layer sites confirmed safe and tagged with
# SEC-PATH-OK: <reason> so future audits trust the inline guard at a glance.
Fifth CI backstop test_route_path_arithmetic_is_safe_joined_or_marked
AST-walks both layers and fails the build on any <dir-like>/<bare variable>
join that doesn't either route through safe_join_under or carry the marker.
The services layer is in scope because it receives values verbatim from the
routes AND from external sources Bambuddy has no control over (the printer
FTP-listing case above).
SECURITY.md gets a fifth rule + a fifth row in the CI test mapping table;
the rule now names the printer FTP-listing case explicitly so future
services-layer audits set the right expectation.
--------------
fix(library): suppress warning storm when bulk-uploading ZIPs of empty/stub STL files
Uploading a ZIP of stub or empty STL files (e.g. the 24-byte
"solid test\nendsolid test" shape) produced one WARNING per file in
stl_thumbnail.py::generate_stl_thumbnail. The warnings were technically
correct - trimesh returns a valid Mesh with zero vertices, the safeguard
matches, and the function returns None so the library entry is still
created without a thumbnail - but the volume turned a successful upload
into thousands of WARNING lines in the journal.
Two changes:
1. The per-file "Failed to load STL or empty mesh" message in
stl_thumbnail.py is now logger.debug instead of logger.warning. It's
a per-file content observation, not an actionable error; the caller
already handles None correctly. The branch now catches the rare
"large enough but trimesh still can't parse it" case, visible in
debug logs without spamming production.
2. New module constant MIN_USABLE_STL_BYTES = 200 (smallest binary STL
with one triangle is 134B, smallest ASCII ~150B; 200 is a safe floor
below any real STL). The three thumbnail call sites in library.py
(extract_zip_file, single-file upload, _backfill_external_stl_thumbnails)
pre-skip files below this size before calling generate_stl_thumbnail.
Stubs never enter the trimesh pipeline at all.
Behavior is unchanged for real STLs: any file >=200 bytes runs through
the existing pipeline, MAX_VERTICES still triggers simplification at
100k vertices for the 256x256 thumbnail render, large files still get
thumbnails.
------------
fix(stl-thumbnail): silence matplotlib first-import noise (writable cache + font_manager log level)
On first STL upload, three matplotlib-internal log lines surfaced:
WARNING [matplotlib] /opt/claude/.config/matplotlib is not a writable directory
INFO [matplotlib.font_manager] Failed to extract font properties from NotoColorEmoji.ttf
INFO [matplotlib.font_manager] generated new fontManager
The writable-dir warning fired because Bambuddy's $HOME isn't writable for
matplotlib's default config path; matplotlib fell back to /tmp/matplotlib-XXX
which lost the font cache on every host reboot, so font_manager rebuilt it
each cold start - producing another batch of INFO lines.
Fix is two small additions in stl_thumbnail.py before the matplotlib import:
1. New _configure_matplotlib_cache() sets MPLCONFIGDIR to
settings.base_dir/.cache/matplotlib (mkdir if missing) so the cache
persists across container restarts and the writable-dir warning never
fires. Respects an externally-set MPLCONFIGDIR so operators who chose
their own path aren't overridden. Best-effort with a debug fallback if
settings can't be imported or the mkdir fails.
2. logging.getLogger("matplotlib.font_manager").setLevel(WARNING) at module
import demotes the per-font INFO scan that fires when font_manager
builds its cache cold. Real font warnings (>= WARNING) still surface.
3 new tests: font_manager logger at WARNING after module import;
_configure_matplotlib_cache creates the directory under base_dir and sets
MPLCONFIGDIR; an externally-set MPLCONFIGDIR is preserved verbatim.
5516 backend tests green, frontend gates clean.
|
||
|
|
e0e597271e |
● feat(#1008): library trash bin, admin bulk purge, auto-purge setting
Library files now move to a configurable-retention trash bin on delete
instead of being hard-deleted from disk (default 30 days). Admins get a
"Purge old" bulk action on the File Manager with a live preview, plus an
optional auto-purge setting in Settings → File Manager that runs the same
operation once per 24h when enabled (default off). Regular users see and
manage their own trashed files; admins see everyone's. External (linked)
files bypass trash since their bytes aren't under Bambuddy's control.
- New `library:purge` permission (admin-only by default)
- Nullable indexed `deleted_at` column on library_files; dialect-aware
ALTER TABLE so the column actually gets added on PostgreSQL (raw
DATETIME is SQLite-only syntax)
- New `LibraryFile.active()` classmethod; every query site routed through
it so trashed rows don't leak into listings, print dispatch, MakerWorld
dedupe, or stats
- Trash page: select-all + bulk restore/delete, per-row checkboxes, wider
layout so datetime columns don't clip
- Auto-purge: 24h throttle via `library_auto_purge_last_run` setting so
the 15-minute sweeper cadence still runs the purge at most once per day
- Save toast wired into every trash/auto-purge setting change
- 17 new backend integration tests (service + routes + auto-purge throttle),
8 new frontend tests, localised across all 8 UI languages
- Wiki + website feature entries updated
|