mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-08-11 00:30:12 -04:00
Two defects, both invisible until you ask the app to stop. Docker never shut down gracefully at all. CMD ["sh","-c","uvicorn ..."] left the shell as PID 1 with uvicorn as its child, and dash does not forward signals, so docker stop SIGTERMed the shell and uvicorn never heard about it. Measured on the shipped image: the full 10s grace period, exit 137, and no "Shutting down" line in the log. Every stop, restart and image update was a hard kill -- no WAL checkpoint, no MQTT disconnect, no virtual-printer teardown. `exec` makes uvicorn PID 1; the rebuilt image now stops in 1s with exit 0 and checkpoints the WAL. Separately, uvicorn's timeout_graceful_shutdown defaults to None -- wait forever for in-flight requests. An MJPEG camera stream is a response that never completes (httptools' connection shutdown() only flips keep_alive on an in-flight cycle, it never closes the transport), so one open camera tile pinned the process until systemd SIGKILLed at 90s. The ordering makes it unfixable from inside the app: uvicorn fires the lifespan shutdown -- the code that tears the streams down -- only after connections drain. All six launchers now pass --timeout-graceful-shutdown 5: Dockerfile, deploy/bambuddy.service, the systemd unit and launchd plist from install/install.sh, the SpoolBuddy installer's unit, and the Windows NSSM registration. On timeout uvicorn cancels the request tasks; the camera generators already unwind cleanly on CancelledError. TimeoutStopSec raised to 30s on the units and stop_grace_period: 30s added to compose, as backstops rather than the mechanism. On Windows NSSM's default 1500ms AppStopMethodConsole was force-killing uvicorn mid-teardown; raised to 15s, with the WM_CLOSE and thread-message stages skipped (uvicorn is a console app with neither a window nor a message loop).
176 lines
9 KiB
YAML
176 lines
9 KiB
YAML
services:
|
||
bambuddy:
|
||
image: ghcr.io/maziggy/bambuddy:latest
|
||
build: .
|
||
# Usage:
|
||
# docker compose up -d → pulls pre-built image from ghcr.io
|
||
# docker compose up -d --build → builds locally from source
|
||
container_name: bambuddy
|
||
# File ownership inside the data and logs volumes is normalised by the
|
||
# entrypoint at startup (chowns to PUID:PGID and drops privileges via
|
||
# gosu before running the app). Override PUID / PGID below to match
|
||
# your host user if needed — defaults to 1000:1000 to match the
|
||
# historical compose `user:` directive.
|
||
#
|
||
# Allow binding to privileged ports (322, 990) as non-root user — required
|
||
# for FTPS in every VP mode and for the RTSPS camera proxy in proxy mode +
|
||
# non-proxy modes that have a target printer configured. Without this, the
|
||
# FTP and RTSP listeners silently fail.
|
||
cap_add:
|
||
- NET_BIND_SERVICE
|
||
#
|
||
# LINUX: Use host mode for printer discovery and camera streaming
|
||
network_mode: host
|
||
#
|
||
# macOS/WINDOWS: Docker Desktop doesn't support host mode.
|
||
# Comment out "network_mode: host" above and uncomment "ports:" below.
|
||
# Note: Printer discovery won't work - add printers manually by IP.
|
||
#ports:
|
||
# - "${PORT:-8000}:8000"
|
||
# - "3000:3000" # Virtual printer bind/detect
|
||
# - "3002:3002" # Virtual printer bind/detect
|
||
# - "8883:8883" # Virtual printer MQTT
|
||
# - "990:990" # Virtual printer FTP control
|
||
# - "6000:6000" # Virtual printer file transfer tunnel
|
||
# - "322:322" # Virtual printer RTSP camera (X1/H2/P2; proxy mode + non-proxy modes with a target printer)
|
||
# - "2024-2026:2024-2026" # Virtual printer proprietary ports (A1/P1S)
|
||
# - "50000-50029:50000-50029" # Virtual printer FTP passive data (3 VPs × 10-port slice)
|
||
#
|
||
# FTP passive-mode port slicing (#1646): non-proxy VPs (Archive / Review /
|
||
# Queue modes) get a 10-port slice each, allocated by VP id — VP 1 →
|
||
# 50000-50009, VP 2 → 50010-50019, VP 3 → 50020-50029, etc. The default
|
||
# exposure above covers 3 VPs; widen the range to cover more
|
||
# (`50000-500N9` where N = vp_count - 1). Proxy-mode VPs forward the
|
||
# real printer's full 50000-50100 range — if you use proxy mode, expose
|
||
# `50000-50100:50000-50100` instead.
|
||
#
|
||
# Why narrow this matters on bridge mode: with Docker's default
|
||
# userland-proxy (true), every exposed port spawns one docker-proxy host
|
||
# process per address family (IPv4 + IPv6). The original 1001-port range
|
||
# spawned ~2000 such processes, pinning ~3.5 GB of host RAM that doesn't
|
||
# appear in `docker stats` (host-level, not container-level). 30 ports
|
||
# → ~60 processes → ~210 MB instead.
|
||
#
|
||
# Bridge mode is the normal setup for any Linux host that runs
|
||
# Bambuddy alongside other services (NAS, multi-tenant Docker VM,
|
||
# Synology DSM, Unraid) — `network_mode: host` would conflict with
|
||
# ports those other services already bind. Bambuddy uses host mode by
|
||
# default because SSDP printer discovery needs L2 multicast, but it's
|
||
# a deliberate trade-off, not a security-blind default; setups that
|
||
# forgo discovery (add printers by IP) can stay on bridge with the
|
||
# narrowed range above.
|
||
volumes:
|
||
- bambuddy_data:/app/data
|
||
- bambuddy_logs:/app/logs
|
||
#
|
||
# OPTIONAL — only needed if you ALSO run a native install of Bambuddy
|
||
# on the same host and want both installs to share the same Virtual
|
||
# Printer CA certificate (so the slicer only has to trust one CA).
|
||
# Most Docker-only users should leave this commented out — the
|
||
# entrypoint will keep the VP data inside the named volume above.
|
||
# If uncommented, the entrypoint chowns the host directory to
|
||
# PUID:PGID on first start so the container user can write to it.
|
||
#- ./virtual_printer:/app/data/virtual_printer
|
||
#
|
||
# Mount scheduled backup output to NAS or external storage
|
||
# Backups default to DATA_DIR/backups/ inside the data volume.
|
||
# Uncomment to store them externally (e.g. on a NAS share).
|
||
#- /path/to/nas/bambuddy-backups:/app/data/backups
|
||
#
|
||
# Tailscale integration (optional): mount the host's tailscaled socket
|
||
# so Bambuddy can request Let's Encrypt certs for virtual printers via
|
||
# your tailnet's MagicDNS name. Requires:
|
||
# 1. Tailscale installed + `tailscale up` completed on the host
|
||
# 2. `sudo tailscale set --operator=<container-user>` on the host so
|
||
# the user running the container can call `tailscale cert`
|
||
# Without this mount, the Tailscale toggle in the UI is harmless —
|
||
# Bambuddy falls back to self-signed certs.
|
||
#- /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock
|
||
#
|
||
# Using a self signed certificate for Home Assistant
|
||
# Add your certificate to certs directory and mount it to the container.
|
||
# The certificate will be added to the system trust store on container startup.
|
||
# Enable the system trust store with the USE_SYSTEM_TRUST_STORE env var to
|
||
# have Bambuddy trust the certificate.
|
||
# - /path/to/certs:/usr/local/share/ca-certificates
|
||
#
|
||
# External library folders. Mount the host paths the operator wants
|
||
# users to be able to register as external folders. The in-container
|
||
# paths chosen here MUST appear in BAMBUDDY_EXTERNAL_ROOTS below.
|
||
# Read-only (:ro) is recommended unless you want users uploading
|
||
# files back to the host share.
|
||
#- /mnt/nas/3d-prints:/external/nas:ro
|
||
#- /srv/library:/external/projects:ro
|
||
environment:
|
||
- TZ=${TZ:-Europe/Berlin}
|
||
# User/group the container drops to after the entrypoint normalises
|
||
# ownership on /app/data and /app/logs. Match your host user (run
|
||
# `id -u` / `id -g`) if you want files written by the container to
|
||
# show up as your user on the host. Defaults to 1000:1000.
|
||
- PUID=${PUID:-1000}
|
||
- PGID=${PGID:-1000}
|
||
# Port BamBuddy runs on (default: 8000)
|
||
# Usage: PORT=8080 docker compose up -d
|
||
- PORT=${PORT:-8000}
|
||
# Virtual printer: Set to the Docker host's IP when using bridge mode (ports:).
|
||
# Required for FTP passive mode to work behind NAT.
|
||
# Example: VIRTUAL_PRINTER_PASV_ADDRESS=192.168.1.100
|
||
#- VIRTUAL_PRINTER_PASV_ADDRESS=
|
||
#
|
||
# External PostgreSQL (optional — uses SQLite by default)
|
||
# Example: DATABASE_URL=postgresql+asyncpg://bambuddy:password@db-host:5432/bambuddy
|
||
#- DATABASE_URL=
|
||
#
|
||
# Slicer API sidecar (optional — Settings → "Use Slicer API" toggles this on).
|
||
# Default points at the OrcaSlicer sidecar on the docker host; change if you
|
||
# run the sidecar on a different host/port. The matching docker-compose.yml
|
||
# for the sidecars lives in the orca-slicer-api fork
|
||
# (https://github.com/maziggy/orca-slicer-api).
|
||
#- SLICER_API_URL=http://localhost:3003
|
||
#
|
||
# MFA at-rest encryption key (#1219). Auto-generated to
|
||
# DATA_DIR/.mfa_encryption_key on first startup if unset. Override here
|
||
# to manage the key out-of-band (e.g. via a secret manager).
|
||
#- MFA_ENCRYPTION_KEY=
|
||
#
|
||
# External library folders (GHSA-r2qv follow-up). Empty default
|
||
# disables the "Add external folder" feature; set to one or more
|
||
# colon-separated absolute paths INSIDE THE CONTAINER to opt in.
|
||
# The paths must also be bind-mounted from the host — uncomment
|
||
# the matching volume snippet below.
|
||
# Example for a single NAS mount:
|
||
#- BAMBUDDY_EXTERNAL_ROOTS=/external/nas
|
||
# Example for two roots:
|
||
#- BAMBUDDY_EXTERNAL_ROOTS=/external/nas:/external/projects
|
||
#
|
||
# Enable System Trust Store for certificate validation (e.g. for local Home Assistant)
|
||
# You also need to mount your certificates to the container (see volumes section above).
|
||
# - USE_SYSTEM_TRUST_STORE=true
|
||
restart: unless-stopped
|
||
# Docker's default is 10s, after which it SIGKILLs. Bambuddy shuts down in
|
||
# well under that (uvicorn caps its wait on in-flight requests at 5s, then
|
||
# the app checkpoints the SQLite WAL, disconnects MQTT and stops the virtual
|
||
# printers), but a Pi with several virtual printers and a slow SD card can be
|
||
# nearer the limit than is comfortable. The headroom costs nothing — the
|
||
# container exits as soon as it is done, not when the timer expires.
|
||
stop_grace_period: 30s
|
||
|
||
# Optional: External PostgreSQL database
|
||
# Uncomment to run Postgres alongside Bambuddy (or use an external Postgres host)
|
||
#postgres:
|
||
# image: postgres:16-alpine
|
||
# container_name: bambuddy-db
|
||
# restart: unless-stopped
|
||
# environment:
|
||
# POSTGRES_USER: bambuddy
|
||
# POSTGRES_PASSWORD: changeme
|
||
# POSTGRES_DB: bambuddy
|
||
# volumes:
|
||
# - bambuddy_pgdata:/var/lib/postgresql/data
|
||
# ports:
|
||
# - "5432:5432"
|
||
|
||
volumes:
|
||
bambuddy_data:
|
||
bambuddy_logs:
|
||
#bambuddy_pgdata:
|