#!/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 # 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 \ --without-telnet \ --without-kubernetes \ --disable-guacenc \ --disable-guaclog \ --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 ' 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 " 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