docs(slicer-api): switch sidecar to pre-built images on GHCR + Docker Hub (#1657)

Local build via Docker git context required `git` in the BuildKit worker,
  which QNAP Container Station and Synology DSM don't provide. Both sidecar
  images are now published to ghcr.io/maziggy/{orca-slicer-api,bambu-studio-api}
  and docker.io/maziggy/{orca-slicer-api,bambu-studio-api}; compose pulls
  :latest by default, SIDECAR_TAG=bambuddy-X.Y.Z pins per release.

  Wiki, slicer-api README, .env.example, and CHANGELOG aligned. Build-from-source
  path kept under "Building from source (advanced)" for forks / dev work.

  Both images are linux/amd64 only — OrcaSlicer ARM64 on hold pending upstream
  fix; BambuStudio doesn't publish ARM64.
This commit is contained in:
maziggy
2026-06-06 13:06:45 +02:00
parent f243e4e598
commit b362f159ac
7 changed files with 138 additions and 46 deletions
+3
View File
@@ -4,6 +4,9 @@ All notable changes to Bambuddy will be documented in this file.
## [0.2.5b1] - Unreleased
### Changed
- **Slicer sidecar now ships as pre-built images on GHCR + Docker Hub — install works on QNAP / Synology / Container Station (#1657, reported by @d3nn3s08)** — Reporter on QNAP QTS 5.2.9 hit three install failures in sequence: the official `slicer-api/docker-compose.yml` used `build: { context: https://github.com/maziggy/orca-slicer-api.git#bambuddy/profile-resolver }`, which requires `git` in the Docker BuildKit worker — Container Station and Synology DSM don't ship git there, so the build fails immediately with `exec: "git": executable file not found`. Manual ZIP-as-local-context workaround tripped a QNAP filesystem quirk in the systemd post-install (`Failed to copy permissions from /etc/group`). Fallback to `ghcr.io/afkfelix/orca-slicer-api:latest-orca2.3.0` ran but couldn't slice — that image lacks the `bambuddy/profile-resolver` patches (the `inherits:` chain resolver, the `from: "User"` → `"system"` rewrite, the `# ` clone-prefix strip, and the sentinel-value strip), so `/profiles/bundled` returned 400 and `/slice` returned `Invalid parameter value(s) included in the 3mf file`. **The fix removes the build-from-source requirement entirely.** Both sidecar images are now built locally on Martin's box and pushed to two registries (`ghcr.io/maziggy/orca-slicer-api`, `docker.io/maziggy/orca-slicer-api`, and the same two for `bambu-studio-api`) via a new `docker-publish-sidecars.sh` helper in the `orca-slicer-api` repo; the stable Bambuddy publish script auto-invokes it after each release, and the beta script too. Daily-beta opts in only via `--include-sidecars` (slicer rebuilds are expensive). The helper has hard safety guards: aborts unless the orca-slicer-api repo is on `bambuddy/profile-resolver` AND the working tree is clean, and never executes `git checkout` / `pull` / `fetch` / `reset` itself. `slicer-api/docker-compose.yml` switches from `build:` to `image: ghcr.io/maziggy/orca-slicer-api:${SIDECAR_TAG:-latest}`. New `SIDECAR_TAG` env var in `.env.example` defaults to `latest`; set `SIDECAR_TAG=bambuddy-X.Y.Z` to pin to the sidecar image that shipped with a specific Bambuddy release. **Scope limitation**: both images are `linux/amd64` only. The OrcaSlicer multi-arch path stays on hold pending an upstream extraction fix — the kldzj/orca-slicer-arm64 AppImage's `--appimage-extract` silently fails under QEMU build emulation; the Dockerfile's `;`-chained RUN block masked the failure until the final `COPY squashfs-root` tripped. ARM64 hosts (Pi 4/5, Apple Silicon Linux) should run the sidecar on a separate x86_64 box and point Bambuddy at it via the **Sidecar URL** field — the sidecar doesn't need to live next to Bambuddy. **Docs aligned**: `slicer-api/README.md` and `wiki/features/slicer-api.md` rewrote the Quick start, Updating, and Sidecar source sections — `docker compose up -d` now pulls instead of building, and `docker compose pull && docker compose up -d` is the new update path (no `--no-cache --pull` dance because Compose only ever sees `image:` references). The build-from-source path stays documented as an advanced option under "Building from source (advanced)" for forks / dev work.
### Changed
- **VP access code is now auto-derived from the target printer in non-proxy modes (Discord report)** — A user on Discord set up a Queue-mode VP with a different access code than the real target printer and couldn't get the slicer to connect, even after the cert-trust path was sorted. Root cause: the live target-printer mirror that landed earlier in the 0.2.5 cycle forwards the slicer's MQTT/RTSPS auth bytes through to the real printer — the slicer holds **one** code in its profile (the one it bound the VP with), and that code has to pass two checks (VP listener, then real printer). If the codes diverge the bridge silently fails at the second hop and the slicer abandons the connection (e.g. opens 8883, FINs before sending a ClientHello). The wiki *did* document a code-match requirement but framed it as a camera-only concern (`MQTT and FTP work either way; only the camera path needs the match`) — wrong, all bridged protocols inherit. **The fix removes the foot-gun rather than re-document it.** When a target printer is selected on a non-proxy VP (Archive / Review / Queue), the access-code field in the VP card switches to a read-only display showing the target's code with an Eye-toggle reveal, and the backend auto-inherits the value on every `create` / `update` (any explicit `access_code` submitted alongside a target is silently overridden — belt-and-braces for non-UI clients). When no target is set, the field stays editable as before. The same `inheritsAccessCodeFromTarget` predicate gates a small "Inherited from target" badge in place of the existing `isSet` / `notSet` status pill. Changing the target after the slicer has already bound triggers an info toast ("Access code now matches the new target — re-add this device in your slicer") because the slicer's stored code is now stale. **One-shot startup migration** in `core/database.py` corrects any pre-existing mismatched VPs on first boot after the upgrade: SELECTs the diverged rows for an INFO log per VP (`VP 'Workshop Queue' (id=3) access code synced from target printer 'X1C #2'` — audit trail for anyone digging through logs), then UPDATEs via correlated subquery (idempotent — the WHERE clause excludes already-synced rows, so re-running is a no-op; portable across SQLite and Postgres). No user-facing banner because there's no action for the user to take — the fix is done, and a previously-stuck bridge now works. **Wiki**: `features/virtual-printer.md` line 1189 flipped from the wrong MQTT/FTP-work-either-way claim to "the bridge forwards slicer auth bytes through; Bambuddy auto-derives so the codes can't diverge", the line-84 tip's "for camera" framing replaced with the broader rule, and the port-table row for RTSP `:322` annotated with "transparent passthrough to the real printer's `:322`, same end-to-end TLS as proxy mode" so the dedicated-bind-IP-vs-passthrough-to-printer apparent contradiction reads as one consistent model. **i18n**: 5 new keys (`accessCode.inheritedFromTarget`, `accessCode.derivedFromTargetHint`, `accessCode.reveal`, `accessCode.hide`, `toast.targetCodeChangedRebind`) translated in all 11 locales (de/en/es/fr/it/ja/ko/pt-BR/tr/zh-CN/zh-TW), no English fallbacks per the project's hard rule.
+24
View File
@@ -43,11 +43,15 @@ NC='\033[0m' # No Color
# Parse arguments
VERSION=""
PARALLEL=false
SKIP_SIDECARS=false
for arg in "$@"; do
case $arg in
--parallel)
PARALLEL=true
;;
--skip-sidecars)
SKIP_SIDECARS=true
;;
*)
if [ -z "$VERSION" ]; then
VERSION="$arg"
@@ -191,3 +195,23 @@ echo " docker pull ${GHCR_IMAGE}:${VERSION}"
echo " docker pull ${GHCR_IMAGE}:beta"
echo ""
echo -e "${YELLOW}Reminder: Set package to Private in GitHub → Packages → Settings${NC}"
# ============================================================
# Sidecar images (orca-slicer-api + bambu-studio-api) — beta channel = GHCR private
# ============================================================
SIDECAR_SCRIPT="/opt/claude/projects/orca-slicer-api/docker-publish-sidecars.sh"
if [ "$SKIP_SIDECARS" = true ]; then
echo ""
echo -e "${YELLOW}Skipping sidecar images (--skip-sidecars).${NC}"
elif [ ! -x "$SIDECAR_SCRIPT" ]; then
echo ""
echo -e "${YELLOW}Sidecar helper not found at ${SIDECAR_SCRIPT} — skipping sidecar build.${NC}"
else
echo ""
echo -e "${GREEN}================================================${NC}"
echo -e "${GREEN} Publishing sidecar images (beta channel)${NC}"
echo -e "${GREEN}================================================${NC}"
SIDECAR_ARGS="--channel beta --version ${VERSION}"
[ "$PARALLEL" = true ] && SIDECAR_ARGS="$SIDECAR_ARGS --parallel"
"$SIDECAR_SCRIPT" $SIDECAR_ARGS
fi
+35 -6
View File
@@ -56,6 +56,7 @@ PARALLEL=false
PUSH_GHCR=true
PUSH_DOCKERHUB=true
SKIP_RELEASE=false
INCLUDE_SIDECARS=false
for arg in "$@"; do
case $arg in
--parallel)
@@ -70,17 +71,22 @@ for arg in "$@"; do
--skip-release)
SKIP_RELEASE=true
;;
--include-sidecars)
INCLUDE_SIDECARS=true
;;
--help|-h)
echo "Usage: $0 [--parallel] [--ghcr-only] [--dockerhub-only] [--skip-release]"
echo "Usage: $0 [--parallel] [--ghcr-only] [--dockerhub-only] [--skip-release] [--include-sidecars]"
echo ""
echo "Build and publish a daily beta Docker image using the APP_VERSION from config.py."
echo ""
echo "Options:"
echo " --parallel Build both architectures simultaneously"
echo " --ghcr-only Only push to GitHub Container Registry"
echo " --dockerhub-only Only push to Docker Hub"
echo " --skip-release Build+push without creating/updating GitHub release"
echo " --help, -h Show this help"
echo " --parallel Build both architectures simultaneously"
echo " --ghcr-only Only push to GitHub Container Registry"
echo " --dockerhub-only Only push to Docker Hub"
echo " --skip-release Build+push without creating/updating GitHub release"
echo " --include-sidecars Also rebuild + push orca-slicer-api and bambu-studio-api"
echo " images (off by default — slicer rebuilds are expensive)"
echo " --help, -h Show this help"
exit 0
;;
*)
@@ -419,3 +425,26 @@ if [ "$PUSH_DOCKERHUB" = true ]; then
echo " docker pull ${DOCKERHUB_IMAGE}:daily"
echo " docker pull ${IMAGE_NAME}:daily # shorthand"
fi
# ============================================================
# Sidecar images (orca-slicer-api + bambu-studio-api) — opt-in for daily
# ============================================================
SIDECAR_SCRIPT="/opt/claude/projects/orca-slicer-api/docker-publish-sidecars.sh"
if [ "$INCLUDE_SIDECARS" != true ]; then
echo ""
echo -e "${YELLOW}Sidecar images not rebuilt (daily default).${NC}"
echo -e "${YELLOW}Pass --include-sidecars to also publish orca-slicer-api + bambu-studio-api.${NC}"
elif [ ! -x "$SIDECAR_SCRIPT" ]; then
echo ""
echo -e "${YELLOW}Sidecar helper not found at ${SIDECAR_SCRIPT} — skipping sidecar build.${NC}"
else
echo ""
echo -e "${GREEN}================================================${NC}"
echo -e "${GREEN} Publishing sidecar images (daily channel)${NC}"
echo -e "${GREEN}================================================${NC}"
SIDECAR_ARGS="--channel daily --version ${VERSION}"
[ "$PARALLEL" = true ] && SIDECAR_ARGS="$SIDECAR_ARGS --parallel"
[ "$PUSH_GHCR" = false ] && SIDECAR_ARGS="$SIDECAR_ARGS --dockerhub-only"
[ "$PUSH_DOCKERHUB" = false ] && SIDECAR_ARGS="$SIDECAR_ARGS --ghcr-only"
"$SIDECAR_SCRIPT" $SIDECAR_ARGS
fi
+26
View File
@@ -49,6 +49,7 @@ VERSION=""
PARALLEL=false
PUSH_GHCR=true
PUSH_DOCKERHUB=true
SKIP_SIDECARS=false
for arg in "$@"; do
case $arg in
--parallel)
@@ -60,6 +61,9 @@ for arg in "$@"; do
--dockerhub-only)
PUSH_GHCR=false
;;
--skip-sidecars)
SKIP_SIDECARS=true
;;
*)
if [ -z "$VERSION" ]; then
VERSION="$arg"
@@ -282,3 +286,25 @@ if [ "$PUSH_DOCKERHUB" = true ]; then
echo " docker pull ${DOCKERHUB_IMAGE}:${VERSION}"
echo " docker pull ${IMAGE_NAME}:${VERSION} # shorthand"
fi
# ============================================================
# Sidecar images (orca-slicer-api + bambu-studio-api)
# ============================================================
SIDECAR_SCRIPT="/opt/claude/projects/orca-slicer-api/docker-publish-sidecars.sh"
if [ "$SKIP_SIDECARS" = true ]; then
echo ""
echo -e "${YELLOW}Skipping sidecar images (--skip-sidecars).${NC}"
elif [ ! -x "$SIDECAR_SCRIPT" ]; then
echo ""
echo -e "${YELLOW}Sidecar helper not found at ${SIDECAR_SCRIPT} — skipping sidecar build.${NC}"
else
echo ""
echo -e "${GREEN}================================================${NC}"
echo -e "${GREEN} Publishing sidecar images (stable channel)${NC}"
echo -e "${GREEN}================================================${NC}"
SIDECAR_ARGS="--channel stable --version ${VERSION}"
[ "$PARALLEL" = true ] && SIDECAR_ARGS="$SIDECAR_ARGS --parallel"
[ "$PUSH_GHCR" = false ] && SIDECAR_ARGS="$SIDECAR_ARGS --dockerhub-only"
[ "$PUSH_DOCKERHUB" = false ] && SIDECAR_ARGS="$SIDECAR_ARGS --ghcr-only"
"$SIDECAR_SCRIPT" $SIDECAR_ARGS
fi
+6 -4
View File
@@ -6,7 +6,9 @@
ORCA_API_PORT=3003
BAMBU_API_PORT=3001
# Slicer versions. Pinned for reproducibility — bump these when you want
# a newer slicer and accept a fresh ~220 MB BambuStudio download.
ORCA_VERSION=2.3.2
BAMBU_VERSION=02.07.01.57
# Sidecar image tag. Default is `latest` (current stable release).
# Pinning options:
# bambuddy-X.Y.Z pin to the sidecar image that shipped with Bambuddy X.Y.Z
# (e.g. SIDECAR_TAG=bambuddy-0.2.5)
# daily track the bambuddy:daily channel
SIDECAR_TAG=latest
+35 -21
View File
@@ -16,7 +16,7 @@ Enable the API path by:
```bash
cd slicer-api/
cp .env.example .env # edit ports / versions if you like
cp .env.example .env # edit ports if you like
# OrcaSlicer only (default profile):
docker compose up -d
@@ -28,9 +28,15 @@ curl http://localhost:3001/health # bambu-studio-api
curl http://localhost:3003/health # orca-slicer-api
```
First build downloads the slicer's AppImage (~110 MB OrcaSlicer, ~220 MB
BambuStudio) and compiles the Node wrapper. Takes 3–8 minutes per service.
Subsequent runs reuse the local image — instant start.
First start pulls pre-built images from GHCR (~110 MB OrcaSlicer,
~220 MB BambuStudio). No local build, no git in the BuildKit worker,
works on QNAP / Synology / Container Station out of the box.
Both images are `linux/amd64` only. OrcaSlicer's ARM64 build is on hold
pending an upstream extraction fix; BambuStudio doesn't publish ARM64
at all. For ARM64 hosts (Raspberry Pi 4/5, Apple Silicon Linux), run
the sidecar on a separate x86_64 box and point Bambuddy at it via the
**Sidecar URL** field — the sidecar doesn't need to live next to Bambuddy.
## Ports
@@ -55,32 +61,40 @@ In the Bambuddy UI: **Settings → Slicer**:
Leaving the URL field blank uses the `SLICER_API_URL` /
`BAMBU_STUDIO_API_URL` environment defaults from Bambuddy's config.
## Where the source lives
## Where the images live
Both images build from the
Pre-built images are published to two registries on every Bambuddy
stable release:
- `ghcr.io/maziggy/orca-slicer-api:latest` / `docker.io/maziggy/orca-slicer-api:latest`
- `ghcr.io/maziggy/bambu-studio-api:latest` / `docker.io/maziggy/bambu-studio-api:latest`
Each release also publishes a versioned tag (`:bambuddy-X.Y.Z`) so you
can pin to the sidecar that shipped alongside a specific Bambuddy
release — set `SIDECAR_TAG=bambuddy-0.2.5` in `.env`.
Both images are built from the
[`maziggy/orca-slicer-api`](https://github.com/maziggy/orca-slicer-api)
fork (`bambuddy/profile-resolver` branch). The Compose file uses
Docker's git build context, so you don't need to clone it manually —
Docker pulls the repo at build time.
The fork patches AFKFelix's upstream wrapper with the `inherits:`
chain resolver, `from: "User"` → `"system"` rewrite, `# ` clone-prefix
strip, and sentinel-value strip — all empirically required to slice
real GUI exports without segfaulting the CLI. Once those land
upstream, this Compose file can be flipped to pull from
`ghcr.io/afkfelix/orca-slicer-api` directly.
fork (`bambuddy/profile-resolver` branch). The fork patches AFKFelix's
upstream wrapper with the `inherits:` chain resolver, `from: "User"`
→ `"system"` rewrite, `# ` clone-prefix strip, and sentinel-value
strip — all empirically required to slice real GUI exports without
segfaulting the CLI. Once those land upstream, the compose file can be
flipped back to `ghcr.io/afkfelix/orca-slicer-api`.
## Updating
Bump the versions in `.env`, then:
```bash
docker compose --profile bambu build --no-cache
docker compose pull
docker compose --profile bambu up -d
```
`--no-cache` is needed because the Dockerfile downloads the AppImage
inline; Docker won't re-fetch it on a version change otherwise.
That's it — Compose pulls the current `:latest` (or whatever
`SIDECAR_TAG` you've pinned to) and recreates the containers.
To roll back to the sidecar that shipped with a previous Bambuddy
release, set `SIDECAR_TAG=bambuddy-X.Y.Z` in `.env` and re-run the two
commands above.
## Troubleshooting
+9 -15
View File
@@ -17,18 +17,17 @@
# docker compose up -d # starts OrcaSlicer only
# docker compose --profile bambu up -d # starts both
#
# First build pulls the source from the fork over git (~5 min — downloads
# the BambuStudio AppImage, ~220 MB) and caches the image locally.
# Subsequent runs reuse the cache. Pin the slicer versions via .env.
# First start pulls pre-built images from GHCR (~110 MB OrcaSlicer,
# ~220 MB BambuStudio). No local build, no git in the BuildKit worker,
# works on QNAP / Synology / Container Station out of the box.
#
# Both images are linux/amd64 only. OrcaSlicer's ARM64 path is on hold
# pending an upstream extraction fix; BambuStudio doesn't publish ARM64
# at all.
services:
orca-slicer-api:
build:
context: https://github.com/maziggy/orca-slicer-api.git#bambuddy/profile-resolver
dockerfile: Dockerfile
args:
ORCA_VERSION: "${ORCA_VERSION:-2.3.2}"
image: bambuddy-orca-slicer-api:orca${ORCA_VERSION:-2.3.2}
image: ghcr.io/maziggy/orca-slicer-api:${SIDECAR_TAG:-latest}
container_name: orca-slicer-api
restart: unless-stopped
ports:
@@ -46,12 +45,7 @@ services:
retries: 3
bambu-studio-api:
build:
context: https://github.com/maziggy/orca-slicer-api.git#bambuddy/profile-resolver
dockerfile: Dockerfile.bambu-studio
args:
BAMBU_VERSION: "${BAMBU_VERSION:-02.07.01.57}"
image: bambuddy-bambu-studio-api:bambu${BAMBU_VERSION:-02.07.01.57}
image: ghcr.io/maziggy/bambu-studio-api:${SIDECAR_TAG:-latest}
container_name: bambu-studio-api
restart: unless-stopped
ports: