diff --git a/Dockerfile b/Dockerfile index 1dc334d3b..d4729d165 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,11 +23,24 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ ffmpeg \ + gnupg \ iproute2 \ libcap2-bin \ openssh-client \ && rm -rf /var/lib/apt/lists/* +# Install the Tailscale CLI only (no tailscaled — the daemon runs on the host). +# Bambuddy calls `tailscale status` / `tailscale cert` via the host's socket, +# which the user mounts in via docker-compose when they want to enable the +# Tailscale integration for virtual printers. Without the socket mount, the +# binary is harmless — the code logs a hint and falls back to self-signed. +RUN curl -fsSL https://pkgs.tailscale.com/stable/debian/trixie.noarmor.gpg \ + -o /usr/share/keyrings/tailscale-archive-keyring.gpg \ + && curl -fsSL https://pkgs.tailscale.com/stable/debian/trixie.tailscale-keyring.list \ + -o /etc/apt/sources.list.d/tailscale.list \ + && apt-get update && apt-get install -y --no-install-recommends tailscale \ + && rm -rf /var/lib/apt/lists/* + # Allow binding to privileged ports (e.g. 990/FTPS) as non-root user. # File capabilities are more reliable than Docker cap_add with user: directive, # which depends on ambient capability support in the container runtime. diff --git a/backend/app/services/virtual_printer/tailscale.py b/backend/app/services/virtual_printer/tailscale.py index 6e4f1cc8a..0e527d9db 100644 --- a/backend/app/services/virtual_printer/tailscale.py +++ b/backend/app/services/virtual_printer/tailscale.py @@ -92,13 +92,21 @@ class TailscaleService: @classmethod def _log_docker_socket_hint(cls) -> None: - """Log a one-time hint when running in Docker without the Tailscale socket mounted.""" + """Log a one-time hint when running in Docker without the Tailscale socket mounted. + + Fires in both states: (a) tailscale binary missing and (b) binary present + but the host socket isn't mounted into the container. The binary alone + can't talk to the daemon — the host's tailscaled socket needs to be + volume-mounted in docker-compose.yml. + """ if cls._docker_hint_logged: return if Path("/.dockerenv").exists() and not Path("/var/run/tailscale/tailscaled.sock").exists(): logger.info( - "Running in Docker but Tailscale socket not found. " - "Mount /var/run/tailscale/tailscaled.sock to enable Tailscale." + "Running in Docker but /var/run/tailscale/tailscaled.sock is not mounted. " + "Add `- /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock` " + "to docker-compose.yml (under volumes:) and run Tailscale on the host to enable " + "Let's Encrypt certs for virtual printers." ) cls._docker_hint_logged = True @@ -157,6 +165,10 @@ class TailscaleService: ) if returncode is None or returncode != 0: + # If the binary is present but the daemon socket is unreachable (e.g. + # Docker without the socket mount), log the actionable hint rather than + # just the opaque CLI stderr. + self._log_docker_socket_hint() return TailscaleStatus( available=False, hostname="", diff --git a/docker-compose.yml b/docker-compose.yml index 358d33037..cfca2f6eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,6 +43,16 @@ services: # 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=` 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 environment: - TZ=${TZ:-Europe/Berlin} # Port BamBuddy runs on (default: 8000)