Files
bambuddy/deploy/bambuddy.service
maziggy 5bbfeefa65 fix(backup): diagnose an unwritable backup path instead of quoting errno 30 (#2544)
Nightly backups to a mounted NAS share ran from May and then stopped, failing
with [Errno 30] Read-only file system. The reporter checked folder permissions
-- correctly: the mount is gid=backup,dir_mode=0775, the service user is in that
group, and his own shell writes to the share fine.

Errno 30 is EROFS. A permission problem is errno 13. EROFS means the filesystem
refused the write, and it refused because we told it to: our systemd unit ships
ProtectSystem=strict, which mounts everything read-only inside the service's
mount namespace and carves back out only ReadWritePaths=<install> <data> <logs>.
A NAS share is not one of those three. Reads are unaffected -- which is why the
UI happily listed his existing backups from the share while being unable to
write a new one -- and his shell is outside the namespace entirely, so every
check he could think to run said the directory was fine.

Both installers write the unit file wholesale, so a ReadWritePaths line added by
hand disappeared on the next install, taking the backups with it. They now back
the old unit up (.bak-<timestamp>) and carry the operator's extra writable paths
forward, reporting which ones they kept. The unit template documents the
carve-out.

The output directory is probed with a real write when it is saved and when the
backup card loads, so an unwritable path is caught there rather than at 03:00
for a week. On failure the card names the cause and hands over the fix with the
operator's path already in it (systemctl edit bambuddy -> ReadWritePaths=...),
and a failed run reports the same diagnosis rather than the raw OSError. EROFS
outside systemd, permission-denied, out-of-space, not-a-directory and missing are
told apart, in all 11 locales.

Docker: a backup path that is not bind-mounted is writable -- the write lands in
the container's ephemeral layer and is lost on the next compose up. The probe
compares the directory's device against the container root and warns, with the
compose snippet that mounts it properly.
2026-07-12 08:44:53 +02:00

90 lines
3.2 KiB
Desktop File

# BamBuddy Systemd Service Template
#
# INSTALLATION:
# 1. Copy this file to /etc/systemd/system/bambuddy.service
# 2. Replace placeholders:
# - INSTALL_PATH: Where BamBuddy is installed (e.g., /opt/bambuddy)
# - SERVICE_USER: User to run as (e.g., bambuddy)
# - DATA_DIR: Data directory (e.g., /opt/bambuddy/data)
# - LOG_DIR: Log directory (e.g., /opt/bambuddy/logs)
# 3. Run: sudo systemctl daemon-reload
# 4. Run: sudo systemctl enable bambuddy
# 5. Run: sudo systemctl start bambuddy
#
# Or use the install script: ./install/install.sh
#
[Unit]
Description=BamBuddy - Bambu Lab Print Management
Documentation=https://github.com/maziggy/bambuddy
After=network.target
[Service]
Type=simple
User=SERVICE_USER
Group=SERVICE_USER
WorkingDirectory=INSTALL_PATH
# Environment file (optional - created by install script)
EnvironmentFile=-INSTALL_PATH/.env
# Use virtual environment
Environment="PATH=INSTALL_PATH/venv/bin:/usr/local/bin:/usr/bin:/bin"
# Server configuration
# --loop asyncio is required: uvloop's SSL layer can silently truncate VP FTP
# uploads on a ragged client close over slow storage (#1896). Do not remove.
#
# --timeout-graceful-shutdown is also required. Uvicorn's default is to wait
# forever for in-flight requests, and an MJPEG camera stream is a response that
# never completes — one open camera tile would hang the stop until systemd gave
# up and SIGKILLed, skipping the WAL checkpoint, the MQTT disconnect and the
# virtual-printer teardown entirely. On timeout uvicorn cancels the request
# tasks; the camera generators unwind cleanly on CancelledError.
ExecStart=INSTALL_PATH/venv/bin/uvicorn backend.app.main:app --host 0.0.0.0 --port ${PORT:-8000} --loop asyncio --timeout-graceful-shutdown 5
# Restart policy
Restart=on-failure
RestartSec=5
# Graceful shutdown. Uvicorn now bounds its own wait at 5s and the app's own
# teardown takes ~1-2s, so this only has to be comfortably longer than that —
# it is the backstop, not the mechanism. The old 10s could clip a slow teardown
# on a Pi with several virtual printers.
TimeoutStopSec=30
# Kill zombie ffmpeg processes (timelapse processing)
ExecStartPre=-/usr/bin/pkill -9 -f "ffmpeg.*bambuddy"
ExecStopPost=-/usr/bin/pkill -9 -f "ffmpeg.*bambuddy"
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=bambuddy
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
# ProtectHome=true hides /home/* and breaks ExecStart when INSTALL_PATH is
# under /home (issue #1685). Default is the safer read-only; flip to true if
# your INSTALL_PATH is outside /home (e.g. /opt/bambuddy).
ProtectHome=read-only
#
# ProtectSystem=strict mounts EVERYTHING outside these three paths read-only for
# this service — including a NAS share you have mounted yourself and can write to
# from your own shell. Writes there fail with EROFS ("Read-only file system"),
# which looks like a permission problem but is not one (issue #2544).
#
# So if you point Scheduled Backups at a directory outside the install, data and
# log dirs, add it here — or better, in a drop-in that survives a reinstall:
#
# sudo systemctl edit bambuddy
# [Service]
# ReadWritePaths=/mnt/your-nas-share
#
ReadWritePaths=DATA_DIR LOG_DIR INSTALL_PATH
[Install]
WantedBy=multi-user.target