Files
rustguac/Dockerfile
T
Dave Kempe 994f138685 packaging: build guacenc and ship it in every package
guacenc, from guacamole-server, converts .guac recordings to video and is
what MP4 downloads (#241) run. Stop passing --disable-guacenc in
build-deb.sh, the release workflow, install.sh, the Dockerfile and
dev.sh, and ship /opt/rustguac/bin/guacenc in the .deb (debian/rules),
the Docker runtime image and the release tarball (install-release.sh
installs it when present). It needs only the ffmpeg libraries guacd is
already built and shipped with, so no new dependencies.

configure leaves guacenc out quietly when an ffmpeg library is missing,
like the protocols, so check-guacd-plugins.sh now also fails the build
when guacenc was not built.
2026-10-18 13:23:14 +11:00

253 lines
9.8 KiB
Docker

# =============================================================================
# Multi-stage Dockerfile for rustguac
#
# Stages:
# 1. guacd-builder — compile guacd from guacamole-server source
# 2. rust-builder — compile rustguac binary
# 3. runtime — minimal image with both binaries + runtime deps
#
# Build:
# docker build -t rustguac .
#
# Run:
# docker run -d -p 8089:8089 rustguac
#
# Run with VDI (Docker desktop containers):
# docker run -d -p 8089:8089 \
# -v /var/run/docker.sock:/var/run/docker.sock \
# --group-add $(getent group docker | cut -d: -f3) \
# rustguac
#
# The image runs both guacd and rustguac under a simple entrypoint script.
# =============================================================================
# ---------------------------------------------------------------------------
# Stage 1: Build guacd from source
# ---------------------------------------------------------------------------
FROM debian:trixie-slim AS guacd-builder
RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf automake libtool pkg-config make gcc g++ git ca-certificates \
libcairo2-dev libjpeg-dev libpng-dev libwebp-dev \
libssh2-1-dev libssl-dev libvncserver-dev \
libpango1.0-dev libpulse-dev \
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev \
libcunit1-dev libtelnet-dev libwebsockets-dev \
uuid-dev freerdp3-dev libspice-client-glib-2.0-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
# Pin to known-good commit to avoid upstream -Werror breakage
RUN git clone https://github.com/apache/guacamole-server.git \
&& cd guacamole-server && git checkout 6719b20d
# Apply patches for FreeRDP 3.x / Debian 13 compatibility
COPY patches/ /build/patches/
COPY scripts/check-guacd-plugins.sh /build/
WORKDIR /build/guacamole-server
RUN for patch in /build/patches/*.patch; do \
[ -f "$patch" ] || continue; \
echo "Applying patch: $(basename "$patch")"; \
git apply "$patch"; \
done
RUN autoreconf -fi
WORKDIR /build/guacd-build
RUN /build/guacamole-server/configure \
--prefix=/opt/rustguac \
--with-ssh \
--with-vnc \
--with-rdp \
--with-spice \
--without-telnet \
--disable-kubernetes \
--disable-guaclog \
--disable-guacclip \
--disable-static \
&& make -j"$(nproc)" \
&& make install \
&& bash /build/check-guacd-plugins.sh /opt/rustguac/lib \
&& mkdir -p /opt/rustguac/lib/freerdp3 \
&& cp /opt/rustguac/lib/libguac*.so* /opt/rustguac/lib/freerdp3/ \
&& { cp /usr/lib/x86_64-linux-gnu/freerdp3/libguac*.so* /opt/rustguac/lib/freerdp3/ 2>/dev/null || true; }
# ---------------------------------------------------------------------------
# Stage 2: Build rustguac
# ---------------------------------------------------------------------------
FROM rust:1-bookworm AS rust-builder
WORKDIR /build
COPY Cargo.toml Cargo.lock ./
COPY build.rs ./
COPY src/ src/
COPY docs/ docs/
COPY static/ static/
RUN cargo build --release
# ---------------------------------------------------------------------------
# Stage 3: Runtime image
# ---------------------------------------------------------------------------
FROM debian:trixie-slim AS runtime
# Runtime libraries for guacd
RUN apt-get update && apt-get install -y --no-install-recommends \
libcairo2 libjpeg62-turbo libpng16-16t64 libwebp7 \
libssh2-1 libssl3t64 libvncclient1 \
libpango-1.0-0 libpulse0 \
libspice-client-glib-2.0-8 \
libavcodec61 libavformat61 libavutil59 libswscale8 \
libtelnet2 libwebsockets19t64 \
libfreerdp3-3 libfreerdp-client3-3 libwinpr3-3 \
# Xvnc + Chromium for web browser sessions
tigervnc-standalone-server \
chromium chromium-sandbox \
x11-utils \
# Minimal runtime utilities
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Install guacd
COPY --from=guacd-builder /opt/rustguac/sbin/ /opt/rustguac/sbin/
# guacenc converts recordings to video for MP4 downloads
COPY --from=guacd-builder /opt/rustguac/bin/guacenc /opt/rustguac/bin/guacenc
COPY --from=guacd-builder /opt/rustguac/lib/ /opt/rustguac/lib/
# Install rustguac binary
COPY --from=rust-builder /build/target/release/rustguac /opt/rustguac/bin/rustguac
# Install static web assets
COPY static/ /opt/rustguac/static/
# Library path for guacd
RUN echo "/opt/rustguac/lib" > /etc/ld.so.conf.d/rustguac.conf && ldconfig
# FreeRDP plugin setup: guacd loads "guac-common-svc" by name, which FreeRDP
# resolves to "guac-common-svc.so" in its plugin path. The build installs it as
# "libguac-common-svc-client.so", so we create a symlink with the expected name.
# We also ensure the system FreeRDP plugin dir exists and contains the plugins.
RUN mkdir -p /usr/lib/x86_64-linux-gnu/freerdp3 && \
if [ -d /opt/rustguac/lib/freerdp3 ]; then \
cp /opt/rustguac/lib/freerdp3/*.so* /usr/lib/x86_64-linux-gnu/freerdp3/ 2>/dev/null; \
ln -sf libguac-common-svc-client.so /opt/rustguac/lib/freerdp3/guac-common-svc.so; \
ln -sf libguac-common-svc-client.so /usr/lib/x86_64-linux-gnu/freerdp3/guac-common-svc.so; \
echo "FreeRDP plugins installed:"; \
ls /usr/lib/x86_64-linux-gnu/freerdp3/guac* /opt/rustguac/lib/freerdp3/guac-common-svc.so 2>/dev/null; \
fi
# Create writable runtime directories
RUN mkdir -p /opt/rustguac/data /opt/rustguac/recordings /opt/rustguac/tls \
/opt/rustguac/certs /opt/rustguac/drives /opt/rustguac/scripts \
/opt/rustguac/vdi-homes
# Chromium policy: web session hardening.
# DeveloperToolsAvailability=0: CDP needed for login scripts. Users can't reach DevTools
# through the UI anyway — chrome://* is in URLBlocklist.
RUN mkdir -p /etc/chromium/policies/managed && \
echo '{"AllowFileSelectionDialogs": false, "PasswordManagerEnabled": true, "ImportSavedPasswords": false, "DeveloperToolsAvailability": 0, "DownloadRestrictions": 3, "PrintingEnabled": false, "EditBookmarksEnabled": false, "BrowserSignin": 0, "SyncDisabled": true, "ExtensionInstallBlocklist": ["*"], "URLBlocklist": ["file://*", "chrome://*", "chrome-extension://*", "view-source:*", "javascript:*"], "URLAllowlist": ["chrome://policy"]}' \
> /etc/chromium/policies/managed/rustguac.json
# Create non-root user with a real home directory (Chromium crashpad needs it)
RUN groupadd -r rustguac && useradd -r -g rustguac -m -d /home/rustguac -s /bin/sh rustguac
# Generate self-signed cert for guacd TLS (internal loopback encryption)
RUN /opt/rustguac/bin/rustguac generate-cert --hostname localhost --out-dir /opt/rustguac/tls
# Default config template (copied to config.toml on first run if not mounted)
RUN cat > /opt/rustguac/config.toml.default <<'EOF'
listen_addr = "0.0.0.0:8089"
guacd_addr = "127.0.0.1:4822"
recording_path = "/opt/rustguac/recordings"
static_path = "/opt/rustguac/static"
db_path = "/opt/rustguac/data/rustguac.db"
session_pending_timeout_secs = 60
xvnc_path = "Xvnc"
chromium_path = "chromium"
display_range_start = 100
display_range_end = 199
[tls]
cert_path = "/opt/rustguac/tls/cert.pem"
key_path = "/opt/rustguac/tls/key.pem"
guacd_cert_path = "/opt/rustguac/tls/cert.pem"
# VDI Docker desktop containers (uncomment to enable)
# Requires: -v /var/run/docker.sock:/var/run/docker.sock
# [vdi]
# enabled = true
# idle_timeout_mins = 60
# home_base = "/opt/rustguac/vdi-homes"
EOF
# Set ownership so the non-root user can write to runtime dirs.
# The top-level dir is chowned (not recursive) so loaders can create config.toml;
# subdirs are chowned recursively for data, certs, etc.
RUN chown rustguac:rustguac /opt/rustguac && \
chown -R rustguac:rustguac /opt/rustguac/data /opt/rustguac/recordings \
/opt/rustguac/tls /opt/rustguac/certs /opt/rustguac/drives \
/opt/rustguac/scripts /opt/rustguac/vdi-homes /opt/rustguac/config.toml.default
# Entrypoint script: starts guacd in background, then rustguac in foreground
RUN cat > /opt/rustguac/entrypoint.sh <<'SCRIPT'
#!/bin/sh
set -e
# Copy default config on first run (if no config file is mounted/present)
CONFIG_PATH="/opt/rustguac/config.toml"
if [ ! -f "$CONFIG_PATH" ]; then
echo "No config.toml found — copying default configuration."
cp /opt/rustguac/config.toml.default "$CONFIG_PATH"
fi
# Create an admin API key on first run. rustguac decides what "first run"
# means: it opens the database named by db_path in the config and creates
# the admin only if there is none yet. Checking for a database file at a fixed
# path here broke any config that moved db_path: every restart then tried to
# create the admin again and failed on the existing one (#240).
ADMIN_OUT=$(/opt/rustguac/bin/rustguac --config "$CONFIG_PATH" add-admin --name docker-admin --if-none)
echo "$ADMIN_OUT"
case "$ADMIN_OUT" in
*"API Key:"*)
echo ""
echo "==> SAVE THE API KEY ABOVE — it is only shown once! <=="
echo ""
;;
esac
# Start guacd in background
echo "Starting guacd..."
LD_LIBRARY_PATH=/opt/rustguac/lib FREERDP_ADDIN_PATH=/opt/rustguac/lib/freerdp3 \
/opt/rustguac/sbin/guacd \
-b 127.0.0.1 -l 4822 -L "${GUACD_LOG_LEVEL:-info}" -f \
-C /opt/rustguac/tls/cert.pem -K /opt/rustguac/tls/key.pem &
GUACD_PID=$!
# Wait briefly to confirm guacd started
sleep 0.5
if ! kill -0 "$GUACD_PID" 2>/dev/null; then
echo "ERROR: guacd failed to start"
exit 1
fi
echo "guacd started (pid=$GUACD_PID)"
# Trap signals to shut down both processes
trap 'kill $GUACD_PID 2>/dev/null; wait; exit 0' TERM INT
# Run rustguac in foreground
echo "Starting rustguac..."
exec /opt/rustguac/bin/rustguac --config "$CONFIG_PATH" serve
SCRIPT
RUN chmod +x /opt/rustguac/entrypoint.sh
WORKDIR /opt/rustguac
EXPOSE 8089
VOLUME ["/opt/rustguac/data", "/opt/rustguac/recordings", "/opt/rustguac/drives", "/opt/rustguac/vdi-homes"]
ENV RUST_LOG=info
ENV GUACD_LOG_LEVEL=info
ENV HOME=/home/rustguac
USER rustguac
ENTRYPOINT ["/opt/rustguac/entrypoint.sh"]