From e927ccefb15b73f7c88aaae35c53e2e1bba2ef90 Mon Sep 17 00:00:00 2001 From: maziggy Date: Fri, 24 Apr 2026 12:01:22 +0200 Subject: [PATCH] feat(docker): Tailscale integration support via host socket mount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the Tailscale CLI to the production image and document how to enable Let's Encrypt cert provisioning for virtual printers from a Docker-deployed Bambuddy. - Dockerfile installs `tailscale` from the official Debian repo. Only the CLI is used at runtime; tailscaled itself stays on the host. The binary is harmless if the socket isn't mounted — the code logs an actionable hint and falls back to self-signed certs. - docker-compose.yml adds a commented-out volume mount for /var/run/tailscale/tailscaled.sock with inline setup instructions. - tailscale.py's docker-socket hint now also fires when the binary is present but the daemon socket is unreachable (i.e. the new Docker pattern), not just when the binary is missing, so users get the actionable "mount the socket" message instead of opaque CLI stderr. Enabling the integration on a Docker host: 1. `curl -fsSL https://tailscale.com/install.sh | sh` on host 2. `sudo tailscale up` 3. `sudo tailscale set --operator=` for the container PUID 4. Uncomment the tailscaled.sock mount in docker-compose.yml 5. `docker compose up -d --force-recreate` 6. Flip the Tailscale toggle on the VP card --- Dockerfile | 13 +++++++++++++ .../app/services/virtual_printer/tailscale.py | 18 +++++++++++++++--- docker-compose.yml | 10 ++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) 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)