Files
Dave Kempe c2c5679cad build(spice): vendor guacd SPICE protocol support (patch 008)
Vendors native SPICE protocol support (libguac-client-spice) from upstream
PR apache/guacamole-server#688 (GUACAMOLE-261) as patch 008, on top of the
pinned guacd (6719b20d) + existing patches. Wires --with-spice and the
libspice-client-glib-2.0-dev build dep into build-deb.sh, install.sh,
Dockerfile, and dev.sh. guacclip is kept in the source but not built
(--disable-guacclip, like guacenc/guaclog); the PR's incidental non-SPICE
terminal.c keyboard change is excluded.

guacd builds green with libguac-client-spice on Debian 13 under -Werror.
rustguac-side wiring (SessionType::Spice / SpiceParams) still to come.
2026-07-24 08:52:02 +10:00

358 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# dev.sh — Build and run rustguac + guacd for local development/testing.
#
# Usage:
# ./dev.sh build-guacd Build guacd from ../guacamole-server into .guacd-build/
# ./dev.sh start-guacd Start guacd in foreground on localhost:4822
# ./dev.sh build Build rustguac (cargo build)
# ./dev.sh run Build and run rustguac
# ./dev.sh start Build guacd (if needed), build rustguac, start both
# ./dev.sh stop Stop backgrounded guacd
# ./dev.sh deps Install build dependencies for guacd (apt, needs sudo)
# ./dev.sh status Show status of guacd and rustguac processes
# ./dev.sh generate-cert Generate self-signed TLS cert for dev (cert.pem + key.pem)
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GUACD_SRC="${SCRIPT_DIR}/../guacamole-server"
GUACD_BUILD="${SCRIPT_DIR}/.guacd-build"
GUACD_PREFIX="${GUACD_BUILD}/install"
GUACD_BIN="${GUACD_PREFIX}/sbin/guacd"
GUACD_PID="${GUACD_BUILD}/guacd.pid"
GUACD_PORT="${GUACD_PORT:-4822}"
GUACD_LOG_LEVEL="${GUACD_LOG_LEVEL:-info}"
RECORDINGS_DIR="${SCRIPT_DIR}/recordings"
DEV_CERT="${SCRIPT_DIR}/cert.pem"
DEV_KEY="${SCRIPT_DIR}/key.pem"
DEV_CONFIG="${SCRIPT_DIR}/config.local.toml"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
info() { echo -e "${GREEN}[dev]${NC} $*"; }
warn() { echo -e "${YELLOW}[dev]${NC} $*"; }
error() { echo -e "${RED}[dev]${NC} $*" >&2; }
#--- Install build dependencies (Debian/Ubuntu) ---
cmd_deps() {
info "Installing guacd build dependencies..."
sudo apt-get update
sudo apt-get install -y \
autoconf automake libtool pkg-config make gcc g++ \
libcairo2-dev libjpeg-dev libpng-dev libossp-uuid-dev libavcodec-dev \
libavformat-dev libavutil-dev libswscale-dev \
libpango1.0-dev libssh2-1-dev libssl-dev \
libvncserver-dev libtelnet-dev libwebsockets-dev \
libpulse-dev libwebp-dev libcunit1-dev \
freerdp3-dev libspice-client-glib-2.0-dev
# uuid-dev is the standard package on Debian; fall back to libossp-uuid-dev
sudo apt-get install -y uuid-dev 2>/dev/null || true
info "Dependencies installed."
}
#--- Apply guacd patches (FreeRDP 3.x / Debian 13 fixes) ---
apply_guacd_patches() {
local src="$1"
local patch_dir="${SCRIPT_DIR}/patches"
if [[ ! -d "$patch_dir" ]]; then
return 0
fi
for patch in "$patch_dir"/*.patch; do
[[ -f "$patch" ]] || continue
if git -C "$src" apply --check "$patch" 2>/dev/null; then
info "Applying patch: $(basename "$patch")"
git -C "$src" apply "$patch"
else
info "Patch already applied or N/A: $(basename "$patch")"
fi
done
}
#--- Build guacd from source ---
cmd_build_guacd() {
if [[ ! -d "$GUACD_SRC" ]]; then
error "guacamole-server source not found at $GUACD_SRC"
error "Expected it at ../guacamole-server relative to rustguac"
exit 1
fi
apply_guacd_patches "$GUACD_SRC"
info "Building guacd from $GUACD_SRC"
info "Build dir: $GUACD_BUILD"
info "Install dir: $GUACD_PREFIX"
mkdir -p "$GUACD_BUILD"
# Run autoreconf in the source tree if configure doesn't exist
if [[ ! -f "$GUACD_SRC/configure" ]]; then
info "Running autoreconf..."
(cd "$GUACD_SRC" && autoreconf -fi)
fi
# Configure out-of-tree build
cd "$GUACD_BUILD"
if [[ ! -f "$GUACD_BUILD/Makefile" ]]; then
info "Configuring..."
"$GUACD_SRC/configure" \
--prefix="$GUACD_PREFIX" \
--with-ssh \
--with-vnc \
--with-rdp \
--with-spice \
--without-telnet \
--without-kubernetes \
--disable-guacenc \
--disable-guaclog \
--disable-guacclip \
--disable-static
fi
info "Compiling..."
make -j"$(nproc)"
info "Installing to $GUACD_PREFIX..."
make install
info "guacd built successfully: $GUACD_BIN"
"$GUACD_BIN" -v
}
#--- Start guacd in foreground ---
cmd_start_guacd() {
if [[ ! -x "$GUACD_BIN" ]]; then
error "guacd not found at $GUACD_BIN"
error "Run: ./dev.sh build-guacd"
exit 1
fi
# Make sure the library path includes our build
export LD_LIBRARY_PATH="${GUACD_PREFIX}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
info "Starting guacd on localhost:$GUACD_PORT (foreground, log_level=$GUACD_LOG_LEVEL)"
exec "$GUACD_BIN" -b 127.0.0.1 -l "$GUACD_PORT" -L "$GUACD_LOG_LEVEL" -f
}
#--- Start guacd in background ---
start_guacd_bg() {
if [[ ! -x "$GUACD_BIN" ]]; then
error "guacd not found at $GUACD_BIN — run: ./dev.sh build-guacd"
exit 1
fi
# Kill existing if running
stop_guacd_quiet
export LD_LIBRARY_PATH="${GUACD_PREFIX}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
info "Starting guacd in background on localhost:$GUACD_PORT..."
"$GUACD_BIN" -b 127.0.0.1 -l "$GUACD_PORT" -L "$GUACD_LOG_LEVEL" -f \
> "${GUACD_BUILD}/guacd.log" 2>&1 &
local pid=$!
echo "$pid" > "$GUACD_PID"
info "guacd started (pid=$pid)"
# Brief wait to check it didn't crash immediately
sleep 0.5
if ! kill -0 "$pid" 2>/dev/null; then
error "guacd failed to start. Log:"
cat "${GUACD_BUILD}/guacd.log"
exit 1
fi
}
#--- Stop backgrounded guacd ---
stop_guacd_quiet() {
if [[ -f "$GUACD_PID" ]]; then
local pid
pid=$(cat "$GUACD_PID")
if kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
fi
rm -f "$GUACD_PID"
fi
# Also kill any stray guacd from our build
pkill -f "$GUACD_BIN" 2>/dev/null || true
}
cmd_stop() {
info "Stopping guacd..."
stop_guacd_quiet
info "Stopped."
}
#--- Build rustguac ---
cmd_build() {
info "Building rustguac..."
cd "$SCRIPT_DIR"
cargo build 2>&1
info "rustguac built."
}
#--- Setup dev database with a dev admin ---
cmd_setup_db() {
local DB_PATH="${SCRIPT_DIR}/rustguac.db"
if [[ -f "$DB_PATH" ]]; then
info "Database already exists at $DB_PATH"
info "Current admins:"
cd "$SCRIPT_DIR"
cargo run -q -- list-admins
return 0
fi
info "Creating dev database and admin..."
cd "$SCRIPT_DIR"
cargo run -q -- add-admin --name dev
echo ""
info "Save the API key above for testing. Example usage:"
info " curl -H 'Authorization: Bearer <key>' http://localhost:8089/api/sessions"
}
#--- Run rustguac ---
cmd_run() {
cmd_build
mkdir -p "$RECORDINGS_DIR"
cmd_setup_db
info "Starting rustguac..."
cd "$SCRIPT_DIR"
if [[ -f "$DEV_CONFIG" ]]; then
info "Using config: $DEV_CONFIG"
exec cargo run -- --config "$DEV_CONFIG"
else
exec cargo run
fi
}
#--- Start everything ---
cmd_start() {
# Build guacd if not already built
if [[ ! -x "$GUACD_BIN" ]]; then
warn "guacd not built yet, building..."
cmd_build_guacd
fi
# Build rustguac
cmd_build
# Start guacd in background
start_guacd_bg
# Ensure recordings dir exists
mkdir -p "$RECORDINGS_DIR"
# Setup dev database if needed
cmd_setup_db
# Run rustguac in foreground (Ctrl+C stops it, then we clean up guacd)
info "Starting rustguac on localhost:8089..."
info "Press Ctrl+C to stop both."
trap 'echo; info "Shutting down..."; pkill -f "target/debug/rustguac" 2>/dev/null; stop_guacd_quiet; exit 0' INT TERM
cd "$SCRIPT_DIR"
if [[ -f "$DEV_CONFIG" ]]; then
info "Using config: $DEV_CONFIG"
cargo run -- --config "$DEV_CONFIG" &
else
cargo run &
fi
local rustguac_pid=$!
wait "$rustguac_pid" 2>/dev/null || true
stop_guacd_quiet
}
#--- Status ---
cmd_status() {
echo "=== guacd ==="
if [[ -f "$GUACD_PID" ]] && kill -0 "$(cat "$GUACD_PID")" 2>/dev/null; then
echo " Running (pid=$(cat "$GUACD_PID"), port=$GUACD_PORT)"
else
echo " Not running"
fi
if [[ -x "$GUACD_BIN" ]]; then
echo " Binary: $GUACD_BIN"
echo " Version: $("$GUACD_BIN" -v 2>&1 || echo unknown)"
else
echo " Not built"
fi
echo ""
echo "=== rustguac ==="
if pgrep -f "target/debug/rustguac" > /dev/null 2>&1; then
echo " Running (pid=$(pgrep -f 'target/debug/rustguac'))"
else
echo " Not running"
fi
if [[ -f "$SCRIPT_DIR/target/debug/rustguac" ]]; then
echo " Binary: $SCRIPT_DIR/target/debug/rustguac"
else
echo " Not built"
fi
}
#--- Generate self-signed TLS certificate for dev ---
cmd_generate_cert() {
if [[ -f "$DEV_CERT" && -f "$DEV_KEY" ]]; then
info "TLS cert already exists: $DEV_CERT"
info "Delete cert.pem and key.pem to regenerate."
return 0
fi
cmd_build
info "Generating self-signed TLS certificate..."
cd "$SCRIPT_DIR"
cargo run -q -- generate-cert --hostname localhost --out-dir "$SCRIPT_DIR"
info "Certificate generated: $DEV_CERT"
info "Private key generated: $DEV_KEY"
echo ""
info "Add to config.local.toml:"
info " [tls]"
info " cert_path = \"$DEV_CERT\""
info " key_path = \"$DEV_KEY\""
}
#--- Main ---
case "${1:-help}" in
deps) cmd_deps ;;
build-guacd) cmd_build_guacd ;;
start-guacd) cmd_start_guacd ;;
build) cmd_build ;;
run) cmd_run ;;
start) cmd_start ;;
stop) cmd_stop ;;
status) cmd_status ;;
setup-db) cmd_build && cmd_setup_db ;;
generate-cert) cmd_generate_cert ;;
help|*)
echo "Usage: $0 <command>"
echo ""
echo "Commands:"
echo " deps Install system build dependencies for guacd (needs sudo)"
echo " build-guacd Build guacd from ../guacamole-server into .guacd-build/"
echo " start-guacd Start guacd in foreground (for debugging)"
echo " build Build rustguac (cargo build)"
echo " run Build and run rustguac only"
echo " start Build everything, start guacd + rustguac together"
echo " stop Stop backgrounded guacd"
echo " setup-db Create dev database and admin API key"
echo " generate-cert Generate self-signed TLS cert+key for dev (localhost)"
echo " status Show process status"
echo ""
echo "Environment:"
echo " GUACD_PORT guacd listen port (default: 4822)"
echo " GUACD_LOG_LEVEL guacd log level: trace/debug/info/warning/error (default: info)"
;;
esac