mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
Follow-up to the asyncssh migration. asyncssh.connect() internally
calls getpass.getuser() for ~/.ssh/config host matching, regardless
of the explicit `username=` passed for the remote login. Under an
arbitrary Docker PUID with no /etc/passwd entry, getpass.getuser()
raises "No username set in the environment" (OSError in Python 3.13+,
previously a bare KeyError).
Fix: set LOGNAME=bambuddy, USER=bambuddy, HOME=/app in the Dockerfile.
getpass.getuser() tries env vars before pwd.getpwuid(), so the lookup
never touches the passwd database and works for any PUID the operator
picks — no helper code, no image rebuild for different UIDs.
Also pass config=[] to asyncssh.connect() so it does not try to load
~/.ssh/config (whose default path needs a resolvable home directory).
An earlier draft of this fix added a Python helper that caught the
KeyError and injected LOGNAME at module import. That was both more
code than needed and broken on Python 3.13, which wraps the KeyError
in an OSError the helper didn't catch — so the module import itself
crashed, producing a 500 on /spoolbuddy/devices/{id}/update. Reverted
in favour of the one-line ENV fix.
85 lines
2.7 KiB
Docker
85 lines
2.7 KiB
Docker
# Build frontend
|
|
FROM node:22-bookworm-slim AS frontend-builder
|
|
|
|
WORKDIR /app/frontend
|
|
|
|
# Copy package files first for better caching
|
|
COPY frontend/package*.json ./
|
|
|
|
# Use cache mount for npm
|
|
RUN --mount=type=cache,target=/root/.npm \
|
|
npm ci
|
|
|
|
COPY frontend/ ./
|
|
RUN npm run build
|
|
|
|
# Production image
|
|
FROM python:3.13-slim
|
|
|
|
WORKDIR /app
|
|
|
|
# Install system dependencies
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
curl \
|
|
ffmpeg \
|
|
iproute2 \
|
|
libcap2-bin \
|
|
openssh-client \
|
|
&& 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.
|
|
RUN setcap cap_net_bind_service=+ep "$(readlink -f /usr/local/bin/python3)"
|
|
|
|
# Install Python dependencies with cache mount
|
|
COPY requirements.txt ./
|
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
|
pip install --root-user-action=ignore -r requirements.txt
|
|
|
|
# Copy backend
|
|
COPY backend/ ./backend/
|
|
|
|
# Copy built frontend from builder stage
|
|
COPY --from=frontend-builder /app/static ./static
|
|
|
|
# Create data directory for persistent storage
|
|
# chmod 777 allows running as non-root user (e.g., with docker compose user: directive)
|
|
RUN mkdir -p /app/data /app/logs && chmod 777 /app/data /app/logs
|
|
|
|
# Environment variables
|
|
ENV PYTHONUNBUFFERED=1
|
|
ENV DATA_DIR=/app/data
|
|
ENV LOG_DIR=/app/logs
|
|
ENV PORT=8000
|
|
# Provide a local username + home for tools that call getpass.getuser() /
|
|
# os.path.expanduser() under arbitrary PUIDs. With `user: "1001:1001"` the
|
|
# stock python:3.13-slim image has no /etc/passwd entry for that UID, so
|
|
# pwd.getpwuid() raises and breaks libraries that do host-level user lookups
|
|
# (notably asyncssh, which uses the local username for ~/.ssh/config host
|
|
# matching during the SpoolBuddy remote-update flow). Setting LOGNAME/USER
|
|
# makes getpass.getuser() resolve via env vars instead of the passwd db;
|
|
# HOME=/app gives a writable home that is guaranteed to exist.
|
|
ENV HOME=/app
|
|
ENV USER=bambuddy
|
|
ENV LOGNAME=bambuddy
|
|
|
|
EXPOSE 322
|
|
EXPOSE 990
|
|
EXPOSE 3000
|
|
EXPOSE 3002
|
|
EXPOSE 6000
|
|
EXPOSE 8000
|
|
EXPOSE 8883
|
|
EXPOSE 50000-50100
|
|
|
|
# Health check (uses PORT env var via shell)
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
|
CMD python -c "import urllib.request, os; urllib.request.urlopen(f'http://localhost:{os.environ.get(\"PORT\", \"8000\")}/health')" || exit 1
|
|
|
|
# Run the application
|
|
# Use standard asyncio loop (uvloop has permission issues in some Docker environments)
|
|
# Port is configurable via PORT environment variable (default: 8000)
|
|
CMD ["sh", "-c", "uvicorn backend.app.main:app --host 0.0.0.0 --port ${PORT:-8000} --loop asyncio"]
|