macOS mixed root-only steps (default /opt path, sudo git clone) with steps
that must not run as root: brew refuses to run as root, and a root-owned
venv/node_modules can't be managed by the launchd agent. The installer now
refuses sudo on macOS, defaults to ~/bambuddy, and drops sudo from the
download/venv/frontend/env/dir steps. A --path under a root-owned parent still
works via a single elevate-and-chown. Linux (service user + systemd) unchanged.
Native (non-Docker) installs launched uvicorn without --loop asyncio, so
uvicorn[standard] auto-selected uvloop. uvloop's SSL layer drops
already-received but still-buffered data when the client closes the data
connection without a TLS close_notify while the reader is flow-control
paused on slow storage. cmd_STOR writes each chunk to disk inside the read
loop, so a slow consumer falls behind, the tail is lost, read() returns a
clean EOF, and the loop exits with no exception -- the server acked 226 for
a file it truncated itself, then archived, queued, and forwarded the corrupt
3MF to the real printer.
Fix in two independent layers:
1. Remove the trigger: add --loop asyncio to every native launch path,
matching the Dockerfile -- deploy/bambuddy.service, install/install.sh
(systemd + launchd), spoolbuddy/install/install.sh, the Windows NSSM
service, README, CONTRIBUTING dev command.
2. Defense in depth (loop-independent): cmd_STOR now validates that a
received .3mf opens as a ZIP (reads the central directory, no
decompression) before replying 226. A truncated/corrupt file is dropped
and answered with 426, and on_file_received never runs -- so a broken
upload surfaces as an immediate slicer-side send error instead of being
archived and pushed to the printer. Scoped to .3mf; other filetypes pass
through unchanged.
install/docker-install.sh::create_install_dir ran `mkdir -p
"$INSTALL_PATH"` without sudo while DEFAULT_INSTALL_PATH was
/opt/bambuddy, root-owned on every Linux distro. set -e then
aborted the whole script before docker compose could pull the
image — anyone running the documented `curl ... | bash` flow as
a normal user hit this on first install.
Fix: try the unprivileged `mkdir -p ... 2>/dev/null` first so
--path ~/bambuddy, /srv/bambuddy and other writable targets don't
trigger a needless password prompt, then fall back to
`sudo mkdir -p` + `sudo chown -R "$USER:$USER"` only when the
first attempt failed. The chown is load-bearing: without it the
script would later try to write docker-compose.yml + .env into a
root-owned dir as the invoking user and cascade further EACCES
failures.
Not changing the default path: install/update.sh and
install/update_macos.sh both default INSTALL_DIR to /opt/bambuddy,
and install/README.md's update flow documents the same — flipping
the install default to ~/bambuddy without coordinating those
would silently break self-service updates for anyone following
the docs verbatim. The default stays /opt/bambuddy; only the
escalation gap closes.
set -e survives the redirected stderr because the `if !` form is
the documented escape hatch for an expected-failure check.
Smoke-tested writable-target, idempotent-rerun, and the
failing-mkdir-then-sudo-fallback branches.
bambuddy.service shipped with ProtectHome=true, which makes /home/* invisible
to the service namespace. Installing into /home/bambuddy/ (instead of the
default /opt/bambuddy/) made ExecStart=/home/bambuddy/venv/bin/uvicorn fail
with status=203/EXEC because systemd couldn't resolve the binary path.
ReadWritePaths=$INSTALL_PATH does not reliably re-expose /home/* subpaths for
exec resolution.
install/install.sh now detects /home/* INSTALL_PATH and emits ProtectHome=read-only;
default /opt/bambuddy installs keep ProtectHome=true. The manual deploy template
defaults to read-only with a comment on when to tighten it.
read-only keeps /home immutable to the service - no security regression, since
ReadWritePaths still gates writes to the install/data/log dirs only.
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.
Strip @mentions from changelog text in docker-publish-daily-beta.sh
so GitHub doesn't auto-generate a "Contributors" section in release
notes. Add --generate-notes=false for extra safety. Also add ports
2024-2026 (A1/P1S proprietary) to the docker-compose.yml bridge-mode
port mapping and update the install script comment.
The closed-source bambu_networking DLL validates TLS connection parameters
and rejects connections where the certificate doesn't match the printer's
real BBL CA certificate. The TLS-terminating proxy presented Bambuddy's
own certificate, causing X1C/X1 prints to silently fail after verify_job.
Switch to transparent TCP proxying for FTP, FileTransfer, Camera, and FTP
data — only MQTT remains TLS-terminated (required for IP rewriting). The
slicer now gets end-to-end TLS directly with the printer's real certificate.
Changes:
- SlicerProxyManager uses TCPProxy for FTP (990), FileTransfer (6000),
Camera (322), and pre-listens on FTP data ports (50000-50100)
- Only MQTT (8883) uses TLSProxy for IP rewriting
- Remove debug logging from MQTT and FTP proxy code
- Fix install.sh missing AmbientCapabilities=CAP_NET_BIND_SERVICE
- Update module docstring, migration docs, README proxy description
- Add tests verifying transparent proxy architecture
When running multiple virtual printers with different access codes on
separate bind IPs, FTP connections were always routed to the wrong VP.
Root cause: the iptables REDIRECT rule (990→9990) rewrites the
destination IP to the incoming interface's primary address. With Linux's
weak host model (arp_filter=0), packets for secondary IPs arrive on the
primary interface, and REDIRECT sends them all to the first VP's FTP
server. MQTT was unaffected because port 8883 had no redirect.
Fix: FTP server now binds directly to port 990 (standard implicit FTPS),
eliminating the iptables redirect entirely. Requires CAP_NET_BIND_SERVICE
(already set in the systemd service file and Docker image).
Also removed a global asyncio set_exception_handler() in the MQTT server
that was overwritten by each VP instance, causing spurious "Unhandled
exception in client_connected_cb" errors on startup.
Changes:
- FTP_PORT: 9990 → 990 (ftp_server.py)
- Removed set_exception_handler() from MQTT server
- Updated Dockerfile, docker-compose.yml port mappings
- Deprecated --redirect-990 in install script
- Updated wiki: removed iptables instructions for all platforms
- Added migration guide (docs/migration-vp-ftp-port.md)
- Added unit tests for port constant and no-global-state invariant
The install script hardcoded origin/main, so beta testers told to
install from a dev branch silently got the stable release instead.
Add a --branch CLI option and interactive prompt (defaults to main).
Invalid branch names are validated via git ls-remote before any work
is done, showing available branches on failure.
The install script hardcoded origin/main, so beta testers told to
install from a dev branch silently got the stable release instead.
Add a --branch CLI option and interactive prompt (defaults to main).
Fresh installs use git clone --branch, existing installs checkout
and reset to the selected branch.
enum.StrEnum was added in Python 3.11, but the documented minimum is
3.10. Add a compatibility shim in backend/app/core/compat.py that falls
back to (str, Enum) on older versions. Updated all 5 import sites and
lowered pyproject.toml target-version to py310.
The Python hash verification in start_bambuddy.bat used a multi-line
`for /f "usebackq"` with a backtick-delimited command split across
lines. Windows CMD cannot parse line breaks inside backtick-delimited
for /f commands, causing "The syntax of the command is incorrect" at
step 1/6. Removed the entire redundant verification block — the
verify_sha256 subroutine already checks the archive against the
pinned hash. The removed block also had a secondary bug: it always
downloaded the amd64 checksum from python.org even on arm64 systems.