From ea72c52a31b69ce5e589bcb515deb2f34a220f27 Mon Sep 17 00:00:00 2001 From: Dave Kempe Date: Sun, 1 Mar 2026 15:05:41 +1100 Subject: [PATCH] v0.5.0: Theme system, ARM64 builds, Docker fixes, dependency updates Theme system: - 6 built-in presets (dark, light, high-contrast, terminal, nord, corporate) - Admin configures preset + per-color overrides in [theme] config section - Client-side theme switching via localStorage (flash-free) - All static pages updated with 28 CSS custom properties Proxy telemetry: - Track which side terminated connection (guacd/browser/cancelled) - Timing-based log levels (guacd close <5s = warning) - Clamp session dimensions to safe ranges (width 640-8192, height 480-8192, DPI 16-384) Docker fixes (#37): - Fix port mismatch: Dockerfile now uses 8089 consistently - Auto-generate admin API key on first run - Add API key setup docs and recordings volume to compose example ARM64 support: - Multi-platform Docker builds (linux/amd64 + linux/arm64) - Native ARM64 .deb and tarball builds via ubuntu-24.04-arm runner Dependency updates: - rustls 0.23.37, chrono 0.4.44, clap 4.5.60, toml 1.0.3 - futures-util 0.3.32, uuid 1.21.0, pulldown-cmark 0.13.1 - actions/upload-artifact v7, actions/download-artifact v8 Also: FreeRDP 3.x NULL deref patch (003), .gitignore for .playwright-mcp/ Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 137 +++++++- .gitignore | 3 + Cargo.lock | 435 ++++++++++++++++++------- Cargo.toml | 2 +- Dockerfile | 16 +- config.example.toml | 34 ++ docs/installation.md | 17 + patches/003-null-guard-disp-size.patch | 31 ++ src/api.rs | 29 +- src/config.rs | 367 +++++++++++++++++++++ src/main.rs | 20 +- src/session.rs | 19 +- src/websocket.rs | 81 ++++- static/addressbook.html | 68 ++-- static/admin.html | 42 ++- static/client.html | 44 ++- static/docs.html | 38 ++- static/index.html | 19 +- static/logo.svg | 7 +- static/recordings.html | 40 ++- static/sessions.html | 59 ++-- static/tokens.html | 40 ++- 22 files changed, 1238 insertions(+), 310 deletions(-) create mode 100644 patches/003-null-guard-disp-size.patch diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1eba213..112ab8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,7 +79,7 @@ jobs: cp ../*.deb . - name: Upload .deb artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: deb-package path: "*.deb" @@ -109,7 +109,7 @@ jobs: "rustguac-${TAG_VERSION}-linux-amd64" - name: Upload tarball artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: tarball path: rustguac-*-linux-amd64.tar.gz @@ -179,14 +179,115 @@ jobs: cp "$RPMBUILD_DIR"/RPMS/*/*.rpm . - name: Upload .rpm artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: rpm-package path: "*.rpm" if-no-files-found: error # --------------------------------------------------------------------------- - # Build and push Docker image + # Build .deb + tarball (ARM64 / Debian 13) + # --------------------------------------------------------------------------- + build-deb-arm64: + name: Build .deb (arm64) + runs-on: ubuntu-24.04-arm + container: debian:trixie + + steps: + - uses: actions/checkout@v6 + + - name: Install build dependencies + run: | + apt-get update + apt-get install -y --no-install-recommends \ + curl ca-certificates gcc g++ pkg-config make git \ + autoconf automake libtool \ + 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 \ + dpkg-dev debhelper fakeroot build-essential + + - name: Install Rust + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + - name: Clone and build guacd + run: | + git clone --depth 1 $GUACD_REPO /tmp/guacamole-server + for patch in patches/*.patch; do + [ -f "$patch" ] || continue + echo "Applying: $patch" + git -C /tmp/guacamole-server apply "$GITHUB_WORKSPACE/$patch" + done + cd /tmp/guacamole-server && autoreconf -fi + mkdir /tmp/guacd-build && cd /tmp/guacd-build + /tmp/guacamole-server/configure \ + --prefix=/opt/rustguac \ + --with-ssh --with-vnc --with-rdp \ + --without-telnet --without-kubernetes \ + --disable-guacenc --disable-guaclog --disable-static + make -j"$(nproc)" + make DESTDIR="$GITHUB_WORKSPACE/debian/staging" install + + - name: Build rustguac + run: $HOME/.cargo/bin/cargo build --release + + - name: Build .deb package + run: | + VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/') + GIT_HASH=$(echo "$GITHUB_SHA" | cut -c1-7) + DEB_VERSION="${VERSION}+g${GIT_HASH}" + cat > debian/changelog < $(date -R) + EOF + dpkg-buildpackage -us -uc -b + cp ../*.deb . + + - name: Upload .deb artifact + uses: actions/upload-artifact@v7 + with: + name: deb-package-arm64 + path: "*.deb" + if-no-files-found: error + + - name: Build release tarball + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + TARBALL_DIR="/tmp/rustguac-${TAG_VERSION}-linux-arm64" + mkdir -p "$TARBALL_DIR/bin" "$TARBALL_DIR/sbin" "$TARBALL_DIR/lib" "$TARBALL_DIR/static" "$TARBALL_DIR/systemd" + + cp target/release/rustguac "$TARBALL_DIR/bin/" + cp debian/staging/opt/rustguac/sbin/guacd "$TARBALL_DIR/sbin/" + cp -a debian/staging/opt/rustguac/lib/*.so* "$TARBALL_DIR/lib/" + cp -r static/* "$TARBALL_DIR/static/" + cp debian/config.toml.default "$TARBALL_DIR/config.toml.default" + cp debian/rustguac.service "$TARBALL_DIR/systemd/" + cp debian/rustguac-guacd.service "$TARBALL_DIR/systemd/" + cp install-release.sh "$TARBALL_DIR/install.sh" + chmod +x "$TARBALL_DIR/install.sh" + [ -d scripts ] && cp -r scripts "$TARBALL_DIR/" || true + + cd /tmp + tar czf "$GITHUB_WORKSPACE/rustguac-${TAG_VERSION}-linux-arm64.tar.gz" \ + "rustguac-${TAG_VERSION}-linux-arm64" + + - name: Upload tarball artifact + uses: actions/upload-artifact@v7 + with: + name: tarball-arm64 + path: rustguac-*-linux-arm64.tar.gz + if-no-files-found: error + + # --------------------------------------------------------------------------- + # Build and push Docker image (amd64 + arm64) # --------------------------------------------------------------------------- build-docker: name: Build Docker image @@ -195,6 +296,9 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Set up QEMU (for ARM64 cross-build) + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -208,10 +312,11 @@ jobs: id: version run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" - - name: Build and push + - name: Build and push (amd64 + arm64) uses: docker/build-push-action@v6 with: context: . + platforms: linux/amd64,linux/arm64 push: true tags: | sol1/rustguac:${{ steps.version.outputs.version }} @@ -224,35 +329,49 @@ jobs: # --------------------------------------------------------------------------- release: name: Create Release - needs: [build-deb, build-rpm, build-docker] + needs: [build-deb, build-deb-arm64, build-rpm, build-docker] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Download deb artifact - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: deb-package path: artifacts/deb-package - name: Download rpm artifact - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: rpm-package path: artifacts/rpm-package - name: Download tarball artifact - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: tarball path: artifacts/tarball + - name: Download deb artifact (arm64) + uses: actions/download-artifact@v8 + with: + name: deb-package-arm64 + path: artifacts/deb-package-arm64 + + - name: Download tarball artifact (arm64) + uses: actions/download-artifact@v8 + with: + name: tarball-arm64 + path: artifacts/tarball-arm64 + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: generate_release_notes: true files: | artifacts/deb-package/*.deb + artifacts/deb-package-arm64/*.deb artifacts/rpm-package/*.rpm artifacts/tarball/*.tar.gz + artifacts/tarball-arm64/*.tar.gz diff --git a/.gitignore b/.gitignore index 40481be..9bf878d 100644 --- a/.gitignore +++ b/.gitignore @@ -45,5 +45,8 @@ debian/rustguac.substvars rpm/staging/ *.rpm +# Playwright MCP artifacts +.playwright-mcp/ + # Claude Code project memory .claude/ diff --git a/Cargo.lock b/Cargo.lock index 32adccf..718482f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,7 +103,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -114,14 +114,20 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] -name = "arc-swap" -version = "1.8.0" +name = "anyhow" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arc-swap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" dependencies = [ "rustversion", ] @@ -202,9 +208,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -213,9 +219,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" dependencies = [ "cc", "cmake", @@ -326,9 +332,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bcrypt-pbkdf" @@ -343,9 +349,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "blake2" @@ -367,9 +373,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eb4cdd6cf1b31d671e9efe75c5d1ec614776856cefbe109ca373554a6d514f" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ "hybrid-array", ] @@ -395,9 +401,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -422,9 +428,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -457,9 +463,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -481,9 +487,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -491,9 +497,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -641,9 +647,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "211f05e03c7d03754740fd9e585de910a095d6b99f8bcfffdef8319fa02a8331" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ "hybrid-array", ] @@ -808,9 +814,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", @@ -830,13 +836,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bf3682cdec91817be507e4aa104314898b95b84d74f3d43882210101a545b6" +checksum = "285743a676ccb6b3e116bc14cc69319b957867930ae9c4822f8e0f54509d7243" dependencies = [ - "block-buffer 0.11.0", + "block-buffer 0.12.0", "const-oid 0.10.2", - "crypto-common 0.2.0", + "crypto-common 0.2.1", ] [[package]] @@ -953,7 +959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1006,6 +1012,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -1033,9 +1045,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "3.2.2" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf68cef89750956493a66a10f512b9e58d9db21f2a573c079c0bdf1207a54a7" +checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0" dependencies = [ "autocfg", "tokio", @@ -1049,9 +1061,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1064,9 +1076,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1074,15 +1086,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1091,15 +1103,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -1108,15 +1120,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" @@ -1126,9 +1138,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1138,7 +1150,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1200,6 +1211,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "ghash" version = "0.5.1" @@ -1275,6 +1299,15 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -1283,7 +1316,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.2.0", ] [[package]] @@ -1492,14 +1525,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -1619,6 +1651,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -1687,7 +1725,7 @@ checksum = "fe44f2bbd99fcb302e246e2d6bcf51aeda346d02a365f80296a07a8c711b6da6" dependencies = [ "argon2", "bcrypt-pbkdf", - "digest 0.11.0", + "digest 0.11.1", "ecdsa", "ed25519-dalek", "hex", @@ -1765,9 +1803,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -1782,6 +1820,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.182" @@ -1921,9 +1965,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -2008,7 +2052,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2305,18 +2349,18 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", @@ -2325,9 +2369,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -2452,6 +2496,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -2494,9 +2548,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" +checksum = "83c41efbf8f90ac44de7f3a868f0867851d261b56291732d0cbf7cceaaeb55a6" dependencies = [ "bitflags", "getopts", @@ -2715,9 +2769,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2726,9 +2780,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" @@ -2822,7 +2876,7 @@ dependencies = [ "const-oid 0.10.2", "crypto-bigint 0.7.0-rc.18", "crypto-primes", - "digest 0.11.0", + "digest 0.11.1", "pkcs1 0.8.0-rc.4", "pkcs8 0.11.0-rc.11", "rand_core 0.10.0-rc-3", @@ -2859,9 +2913,9 @@ dependencies = [ [[package]] name = "russh" -version = "0.57.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fe22d10a0e39c1134a971d5b8db8a40357b48ef22d81fa8d6eac22202dd782" +checksum = "afe62631a04a1f4d71a14b99505483b95ff97c503b67d876c042fce659186956" dependencies = [ "aes", "aws-lc-rs", @@ -2961,7 +3015,7 @@ dependencies = [ [[package]] name = "rustguac" -version = "0.4.1" +version = "0.5.0" dependencies = [ "axum", "axum-server", @@ -3013,9 +3067,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "log", @@ -3066,9 +3120,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "salsa20" @@ -3236,9 +3290,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" dependencies = [ "base64 0.22.1", "chrono", @@ -3255,9 +3309,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" dependencies = [ "darling", "proc-macro2", @@ -3294,7 +3348,7 @@ checksum = "3b167252f3c126be0d8926639c4c4706950f01445900c4b3db0fd7e89fcb750a" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0", + "digest 0.11.1", ] [[package]] @@ -3316,7 +3370,7 @@ checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0", + "digest 0.11.1", ] [[package]] @@ -3360,7 +3414,7 @@ version = "3.0.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a96996ccff7dfa16f052bd995b4cecc72af22c35138738dc029f0ead6608d" dependencies = [ - "digest 0.11.0", + "digest 0.11.1", "rand_core 0.10.0-rc-3", ] @@ -3509,9 +3563,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3740,9 +3794,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.1+spec-1.1.0" +version = "1.0.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe30f93627849fa362d4a602212d41bb237dc2bd0f8ba0b2ce785012e124220" +checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" dependencies = [ "indexmap 2.13.0", "serde_core", @@ -3764,9 +3818,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.8+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -3779,9 +3833,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", "axum", @@ -3997,9 +4051,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" @@ -4007,6 +4061,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -4068,11 +4128,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -4121,10 +4181,19 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen" -version = "0.2.108" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -4135,9 +4204,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -4149,9 +4218,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4159,9 +4228,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -4172,18 +4241,52 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] [[package]] -name = "web-sys" -version = "0.3.85" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -4507,6 +4610,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -4566,18 +4751,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.37" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.37" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", @@ -4660,6 +4845,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1966f8ac2c1f76987d69a74d0e0f929241c10e78136434e3be70ff7f58f64214" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index af861bb..3109241 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustguac" -version = "0.4.1" +version = "0.5.0" edition = "2021" description = "Lightweight Rust replacement for Apache Guacamole client" diff --git a/Dockerfile b/Dockerfile index 2aa10a2..4dafdfc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ # docker build -t rustguac . # # Run: -# docker run -d -p 8080:8080 rustguac +# docker run -d -p 8089:8089 rustguac # # The image runs both guacd and rustguac under a simple entrypoint script. # ============================================================================= @@ -114,7 +114,7 @@ RUN /opt/rustguac/bin/rustguac generate-cert --hostname localhost --out-dir /opt # Default config (guacd TLS enabled by default) RUN cat > /opt/rustguac/config.toml <<'EOF' -listen_addr = "0.0.0.0:8080" +listen_addr = "0.0.0.0:8089" guacd_addr = "127.0.0.1:4822" recording_path = "/opt/rustguac/recordings" static_path = "/opt/rustguac/static" @@ -136,6 +136,16 @@ RUN cat > /opt/rustguac/entrypoint.sh <<'SCRIPT' #!/bin/sh set -e +# Create admin API key on first run (if no DB exists yet) +DB_PATH="/opt/rustguac/data/rustguac.db" +if [ ! -f "$DB_PATH" ]; then + echo "First run detected — creating admin API key..." + /opt/rustguac/bin/rustguac --config /opt/rustguac/config.toml add-admin --name docker-admin + echo "" + echo "==> SAVE THE API KEY ABOVE — it is only shown once! <==" + echo "" +fi + # Start guacd in background echo "Starting guacd..." LD_LIBRARY_PATH=/opt/rustguac/lib /opt/rustguac/sbin/guacd \ @@ -161,7 +171,7 @@ SCRIPT RUN chmod +x /opt/rustguac/entrypoint.sh WORKDIR /opt/rustguac -EXPOSE 8080 +EXPOSE 8089 VOLUME ["/opt/rustguac/data", "/opt/rustguac/recordings"] ENV RUST_LOG=info diff --git a/config.example.toml b/config.example.toml index cad00c3..c59c596 100644 --- a/config.example.toml +++ b/config.example.toml @@ -178,6 +178,40 @@ web_allowed_networks = ["127.0.0.0/8", "::1/128"] # max_recordings = 0 # global max recording count (0 = unlimited) # rotation_interval_secs = 300 # how often to check rotation (seconds, default: 300) +# ─── Theme ──────────────────────────────────────────────────────────────────── +# +# Customize the web UI appearance. Pick a built-in preset and optionally +# override individual colors. Users can switch themes in the browser via +# the theme switcher (persisted in localStorage). +# +# Built-in presets: dark (default), light, high-contrast, terminal, nord, corporate +# +# All color values are CSS color strings (hex, rgb(), hsl(), named colors). + +# [theme] +# preset = "dark" # built-in preset to use as the base +# logo_url = "/logo.svg" # custom logo URL (shown on login + nav) +# +# # Override individual colors on top of the preset: +# # primary_color = "#e94560" +# # accent_color = "#5bc0be" +# # bg_color = "#1a1a2e" +# # surface_color = "#16213e" +# # text_color = "#e0e0e0" +# # text_muted = "#aaa" +# # border_color = "#333" +# # input_color = "#0f3460" +# # primary_hover = "#c73652" +# # accent_hover = "#4aa3a1" +# # text_dim = "#888" +# # text_on_primary = "#fff" +# # btn_disabled = "#555" +# # status_pending = "#f0c040" +# # status_active = "#5bc0be" +# # status_completed = "#888" +# # status_error = "#e94560" +# # status_expired = "#666" + # ─── Drive / File Transfer ────────────────────────────────────────────────── # # Enables file transfer for RDP (drive redirection) and SSH (SFTP) sessions. diff --git a/docs/installation.md b/docs/installation.md index ec2ce33..7a86690 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -118,6 +118,21 @@ The Docker image: - Enables TLS between rustguac and guacd by default - Exposes HTTP on port 8089 (put a reverse proxy in front for HTTPS) +### API key setup + +On first run (when no database exists), the container automatically generates an admin API key and prints it to the logs: + +```bash +docker logs rustguac +``` + +Save the printed key — it is only shown once. To generate additional keys later: + +```bash +docker exec rustguac /opt/rustguac/bin/rustguac \ + --config /opt/rustguac/config.toml add-admin --name my-admin +``` + ### Docker Compose example ```yaml @@ -128,11 +143,13 @@ services: - "8089:8089" volumes: - rustguac-data:/opt/rustguac/data + - rustguac-recordings:/opt/rustguac/recordings environment: - RUST_LOG=info volumes: rustguac-data: + rustguac-recordings: ``` ## Option D: RPM package diff --git a/patches/003-null-guard-disp-size.patch b/patches/003-null-guard-disp-size.patch new file mode 100644 index 0000000..430e65a --- /dev/null +++ b/patches/003-null-guard-disp-size.patch @@ -0,0 +1,31 @@ +diff --git a/src/protocols/rdp/channels/disp.c b/src/protocols/rdp/channels/disp.c +index 0293843c..a1b2c3d4 100644 +--- a/src/protocols/rdp/channels/disp.c ++++ b/src/protocols/rdp/channels/disp.c +@@ -161,6 +161,10 @@ void guac_rdp_disp_load_plugin(rdpContext* context) { + void guac_rdp_disp_set_size(guac_rdp_disp* disp, guac_rdp_settings* settings, + freerdp* rdp_inst, int width, int height) { + ++ /* Abort if display module or settings are not yet initialized */ ++ if (disp == NULL || settings == NULL) ++ return; ++ + guac_rect resize = { + .left = 0, + .top = 0, +diff --git a/src/protocols/rdp/input.c b/src/protocols/rdp/input.c +index 06bfac13..c8e1d2f7 100644 +--- a/src/protocols/rdp/input.c ++++ b/src/protocols/rdp/input.c +@@ -105,6 +105,11 @@ int guac_rdp_user_size_handler(guac_user* user, int width, int height) { + guac_rdp_settings* settings = rdp_client->settings; + freerdp* rdp_inst = rdp_client->rdp_inst; + ++ /* Abort if not yet fully initialized (browser may send size instruction ++ * before the RDP connection is fully established) */ ++ if (settings == NULL || rdp_client->disp == NULL) ++ return 0; ++ + /* Convert client pixels to remote pixels */ + width = width * settings->resolution / user->info.optimal_resolution; + height = height * settings->resolution / user->info.optimal_resolution; diff --git a/src/api.rs b/src/api.rs index 9b075c0..1076330 100644 --- a/src/api.rs +++ b/src/api.rs @@ -206,11 +206,17 @@ pub async fn auth_status( Extension(site_title): Extension, Extension(theme): Extension, ) -> impl IntoResponse { - let mut resp = json!({ "oidc_enabled": oidc_enabled.0, "site_title": site_title.0 }); - if let Some(ref t) = theme.0 { - if let Ok(v) = serde_json::to_value(t) { - resp["theme"] = v; - } + let mut resp = json!({ + "oidc_enabled": oidc_enabled.0, + "site_title": site_title.0, + }); + resp["theme"] = json!({ + "admin_preset": theme.admin_preset, + "admin_colors": theme.admin_colors, + "presets": theme.presets, + }); + if let Some(ref url) = theme.logo_url { + resp["theme"]["logo_url"] = json!(url); } Json(resp) } @@ -219,9 +225,18 @@ pub async fn auth_status( #[derive(Clone)] pub struct SiteTitle(pub String); -/// Theme configuration from config, shared via Extension. +/// Resolved theme data shared via Extension. #[derive(Clone)] -pub struct ThemeData(pub Option); +pub struct ThemeData { + /// Admin-configured preset name (e.g. "dark"). + pub admin_preset: String, + /// Fully-resolved admin theme colors (preset + overrides). + pub admin_colors: crate::config::ThemeColors, + /// Optional custom logo URL. + pub logo_url: Option, + /// All built-in preset palettes for client-side switching. + pub presets: std::collections::HashMap, +} /// Marker for whether OIDC is configured. #[derive(Clone)] diff --git a/src/config.rs b/src/config.rs index bde02db..a1f6804 100644 --- a/src/config.rs +++ b/src/config.rs @@ -243,8 +243,281 @@ pub struct Config { pub recording: Option, } +/// Fully-resolved theme palette with all 26 color fields. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ThemeColors { + pub primary: String, + pub primary_hover: String, + pub accent: String, + pub accent_hover: String, + pub bg: String, + pub surface: String, + pub input: String, + pub text: String, + pub text_muted: String, + pub border: String, + pub text_dim: String, + pub text_on_primary: String, + pub btn_disabled: String, + pub status_pending: String, + pub status_active: String, + pub status_completed: String, + pub status_error: String, + pub status_expired: String, + pub type_ssh_bg: String, + pub type_ssh_fg: String, + pub type_rdp_bg: String, + pub type_rdp_fg: String, + pub type_vnc_bg: String, + pub type_vnc_fg: String, + pub type_web_bg: String, + pub type_web_fg: String, + pub hop_bg: String, + pub hop_fg: String, +} + +/// Returns all 6 built-in theme presets. +pub fn builtin_presets() -> Vec<(&'static str, ThemeColors)> { + vec![ + ( + "dark", + ThemeColors { + primary: "#e94560".into(), + primary_hover: "#c73652".into(), + accent: "#5bc0be".into(), + accent_hover: "#4aa3a1".into(), + bg: "#1a1a2e".into(), + surface: "#16213e".into(), + input: "#0f3460".into(), + text: "#e0e0e0".into(), + text_muted: "#aaa".into(), + border: "#333".into(), + text_dim: "#666".into(), + text_on_primary: "#fff".into(), + btn_disabled: "#555".into(), + status_pending: "#f0c040".into(), + status_active: "#5bc0be".into(), + status_completed: "#888".into(), + status_error: "#e94560".into(), + status_expired: "#666".into(), + type_ssh_bg: "#1b4332".into(), + type_ssh_fg: "#52b788".into(), + type_rdp_bg: "#3d1f00".into(), + type_rdp_fg: "#f0a050".into(), + type_vnc_bg: "#2d1b4e".into(), + type_vnc_fg: "#b07ff0".into(), + type_web_bg: "#1a1a4e".into(), + type_web_fg: "#7b8ff0".into(), + hop_bg: "#1b4332".into(), + hop_fg: "#52b788".into(), + }, + ), + ( + "light", + ThemeColors { + primary: "#2563eb".into(), + primary_hover: "#1d4ed8".into(), + accent: "#0d9488".into(), + accent_hover: "#0f766e".into(), + bg: "#f8fafc".into(), + surface: "#fff".into(), + input: "#f1f5f9".into(), + text: "#1e293b".into(), + text_muted: "#64748b".into(), + border: "#e2e8f0".into(), + text_dim: "#94a3b8".into(), + text_on_primary: "#fff".into(), + btn_disabled: "#cbd5e1".into(), + status_pending: "#d97706".into(), + status_active: "#0d9488".into(), + status_completed: "#94a3b8".into(), + status_error: "#dc2626".into(), + status_expired: "#cbd5e1".into(), + type_ssh_bg: "#dcfce7".into(), + type_ssh_fg: "#166534".into(), + type_rdp_bg: "#ffedd5".into(), + type_rdp_fg: "#9a3412".into(), + type_vnc_bg: "#f3e8ff".into(), + type_vnc_fg: "#6b21a8".into(), + type_web_bg: "#dbeafe".into(), + type_web_fg: "#1e40af".into(), + hop_bg: "#dcfce7".into(), + hop_fg: "#166534".into(), + }, + ), + ( + "high-contrast", + ThemeColors { + primary: "#ff6b6b".into(), + primary_hover: "#ff4444".into(), + accent: "#00ffcc".into(), + accent_hover: "#00ddaa".into(), + bg: "#000".into(), + surface: "#111".into(), + input: "#1a1a1a".into(), + text: "#fff".into(), + text_muted: "#ccc".into(), + border: "#555".into(), + text_dim: "#999".into(), + text_on_primary: "#000".into(), + btn_disabled: "#444".into(), + status_pending: "#ffdd00".into(), + status_active: "#00ffcc".into(), + status_completed: "#999".into(), + status_error: "#ff4444".into(), + status_expired: "#666".into(), + type_ssh_bg: "#003300".into(), + type_ssh_fg: "#00ff66".into(), + type_rdp_bg: "#332200".into(), + type_rdp_fg: "#ffaa00".into(), + type_vnc_bg: "#220033".into(), + type_vnc_fg: "#cc66ff".into(), + type_web_bg: "#000033".into(), + type_web_fg: "#6699ff".into(), + hop_bg: "#003300".into(), + hop_fg: "#00ff66".into(), + }, + ), + ( + "terminal", + ThemeColors { + primary: "#f59e0b".into(), + primary_hover: "#d97706".into(), + accent: "#22c55e".into(), + accent_hover: "#16a34a".into(), + bg: "#0a0a0a".into(), + surface: "#141414".into(), + input: "#1e1e1e".into(), + text: "#33ff33".into(), + text_muted: "#22aa22".into(), + border: "#2a2a2a".into(), + text_dim: "#186818".into(), + text_on_primary: "#000".into(), + btn_disabled: "#333".into(), + status_pending: "#f59e0b".into(), + status_active: "#33ff33".into(), + status_completed: "#22aa22".into(), + status_error: "#ff3333".into(), + status_expired: "#186818".into(), + type_ssh_bg: "#0a200a".into(), + type_ssh_fg: "#33ff33".into(), + type_rdp_bg: "#201a0a".into(), + type_rdp_fg: "#f59e0b".into(), + type_vnc_bg: "#1a0a20".into(), + type_vnc_fg: "#cc66ff".into(), + type_web_bg: "#0a0a20".into(), + type_web_fg: "#6699ff".into(), + hop_bg: "#0a200a".into(), + hop_fg: "#33ff33".into(), + }, + ), + ( + "nord", + ThemeColors { + primary: "#88c0d0".into(), + primary_hover: "#81a1c1".into(), + accent: "#a3be8c".into(), + accent_hover: "#8fbcbb".into(), + bg: "#2e3440".into(), + surface: "#3b4252".into(), + input: "#434c5e".into(), + text: "#eceff4".into(), + text_muted: "#d8dee9".into(), + border: "#4c566a".into(), + text_dim: "#7b88a1".into(), + text_on_primary: "#2e3440".into(), + btn_disabled: "#4c566a".into(), + status_pending: "#ebcb8b".into(), + status_active: "#a3be8c".into(), + status_completed: "#7b88a1".into(), + status_error: "#bf616a".into(), + status_expired: "#4c566a".into(), + type_ssh_bg: "#384838".into(), + type_ssh_fg: "#a3be8c".into(), + type_rdp_bg: "#483e38".into(), + type_rdp_fg: "#ebcb8b".into(), + type_vnc_bg: "#3e3848".into(), + type_vnc_fg: "#b48ead".into(), + type_web_bg: "#384048".into(), + type_web_fg: "#88c0d0".into(), + hop_bg: "#384838".into(), + hop_fg: "#a3be8c".into(), + }, + ), + ( + "corporate", + ThemeColors { + primary: "#3b82f6".into(), + primary_hover: "#2563eb".into(), + accent: "#f97316".into(), + accent_hover: "#ea580c".into(), + bg: "#0f172a".into(), + surface: "#1e293b".into(), + input: "#334155".into(), + text: "#f1f5f9".into(), + text_muted: "#94a3b8".into(), + border: "#475569".into(), + text_dim: "#64748b".into(), + text_on_primary: "#fff".into(), + btn_disabled: "#475569".into(), + status_pending: "#fbbf24".into(), + status_active: "#34d399".into(), + status_completed: "#64748b".into(), + status_error: "#ef4444".into(), + status_expired: "#475569".into(), + type_ssh_bg: "#14532d".into(), + type_ssh_fg: "#4ade80".into(), + type_rdp_bg: "#431407".into(), + type_rdp_fg: "#fb923c".into(), + type_vnc_bg: "#3b0764".into(), + type_vnc_fg: "#c084fc".into(), + type_web_bg: "#172554".into(), + type_web_fg: "#60a5fa".into(), + hop_bg: "#14532d".into(), + hop_fg: "#4ade80".into(), + }, + ), + ( + "avocado", + ThemeColors { + primary: "#d4883c".into(), + primary_hover: "#b8742f".into(), + accent: "#c5d455".into(), + accent_hover: "#a8b83e".into(), + bg: "#151a0e".into(), + surface: "#1e2414".into(), + input: "#2a321c".into(), + text: "#eef0e0".into(), + text_muted: "#a0a888".into(), + border: "#3a4228".into(), + text_dim: "#5a6240".into(), + text_on_primary: "#151a0e".into(), + btn_disabled: "#3a4228".into(), + status_pending: "#d4883c".into(), + status_active: "#c5d455".into(), + status_completed: "#6a7252".into(), + status_error: "#c0392b".into(), + status_expired: "#3a4228".into(), + type_ssh_bg: "#1e2a14".into(), + type_ssh_fg: "#8cb832".into(), + type_rdp_bg: "#2a2014".into(), + type_rdp_fg: "#d4a050".into(), + type_vnc_bg: "#221e2a".into(), + type_vnc_fg: "#b07ff0".into(), + type_web_bg: "#1a1e2a".into(), + type_web_fg: "#7b8ff0".into(), + hop_bg: "#1e2a14".into(), + hop_fg: "#8cb832".into(), + }, + ), + ] +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct ThemeConfig { + /// Built-in preset name: dark, light, high-contrast, terminal, nord, corporate. + #[serde(skip_serializing_if = "Option::is_none")] + pub preset: Option, #[serde(skip_serializing_if = "Option::is_none")] pub primary_color: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -266,9 +539,103 @@ pub struct ThemeConfig { #[serde(skip_serializing_if = "Option::is_none")] pub border_color: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub text_dim: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub text_on_primary: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub btn_disabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub status_pending: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub status_active: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub status_completed: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub status_error: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub status_expired: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_ssh_bg: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_ssh_fg: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_rdp_bg: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_rdp_fg: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_vnc_bg: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_vnc_fg: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_web_bg: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_web_fg: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub hop_bg: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub hop_fg: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub logo_url: Option, } +impl ThemeConfig { + /// Resolve config into a full ThemeColors palette. + /// Starts from the named preset (default: "dark"), then applies overrides. + pub fn resolve(&self) -> (String, ThemeColors) { + let preset_name = self.preset.as_deref().unwrap_or("dark"); + let presets = builtin_presets(); + let mut colors = presets + .iter() + .find(|(name, _)| *name == preset_name) + .map(|(_, c)| c.clone()) + .unwrap_or_else(|| presets[0].1.clone()); + + macro_rules! apply { + ($field:ident, $src:ident) => { + if let Some(ref v) = self.$src { + colors.$field = v.clone(); + } + }; + ($field:ident) => { + if let Some(ref v) = self.$field { + colors.$field = v.clone(); + } + }; + } + + apply!(primary, primary_color); + apply!(primary_hover); + apply!(accent, accent_color); + apply!(accent_hover); + apply!(bg, bg_color); + apply!(surface, surface_color); + apply!(input, input_color); + apply!(text, text_color); + apply!(text_muted); + apply!(border, border_color); + apply!(text_dim); + apply!(text_on_primary); + apply!(btn_disabled); + apply!(status_pending); + apply!(status_active); + apply!(status_completed); + apply!(status_error); + apply!(status_expired); + apply!(type_ssh_bg); + apply!(type_ssh_fg); + apply!(type_rdp_bg); + apply!(type_rdp_fg); + apply!(type_vnc_bg); + apply!(type_vnc_fg); + apply!(type_web_bg); + apply!(type_web_fg); + apply!(hop_bg); + apply!(hop_fg); + + (preset_name.to_string(), colors) + } +} + fn default_listen_addr() -> String { "127.0.0.1:8089".into() } diff --git a/src/main.rs b/src/main.rs index 8ca5c0e..fa27a61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -559,7 +559,25 @@ async fn run_server(config: Config, database: Db) { let oidc_enabled = OidcEnabled(oidc_state.is_some()); let vault_configured = VaultConfigured(config.vault.is_some()); let site_title = SiteTitle(config.site_title.clone()); - let theme_data = ThemeData(config.theme.clone()); + let theme_data = { + let (admin_preset, admin_colors) = config + .theme + .as_ref() + .map(|t| t.resolve()) + .unwrap_or_else(|| ("dark".into(), crate::config::builtin_presets()[0].1.clone())); + let logo_url = config.theme.as_ref().and_then(|t| t.logo_url.clone()); + let presets: std::collections::HashMap = + crate::config::builtin_presets() + .into_iter() + .map(|(name, colors)| (name.to_string(), colors)) + .collect(); + ThemeData { + admin_preset, + admin_colors, + logo_url, + presets, + } + }; let trusted_proxies = auth::TrustedProxies(config.trusted_proxies.clone()); // Periodically clean up expired auth sessions from the database diff --git a/src/session.rs b/src/session.rs index 6f7204a..1760ca3 100644 --- a/src/session.rs +++ b/src/session.rs @@ -274,9 +274,20 @@ impl SessionManager { created_by: String, ) -> Result { let session_id = Uuid::new_v4(); - let width = req.width.unwrap_or(1920); - let height = req.height.unwrap_or(1080); - let dpi = req.dpi.unwrap_or(96); + let raw_width = req.width.unwrap_or(1920); + let raw_height = req.height.unwrap_or(1080); + let raw_dpi = req.dpi.unwrap_or(96); + let width = raw_width.clamp(640, 8192); + let height = raw_height.clamp(480, 8192); + let dpi = raw_dpi.clamp(16, 384); + if width != raw_width || height != raw_height || dpi != raw_dpi { + tracing::warn!( + session_id = %session_id, + raw_width, raw_height, raw_dpi, + clamped_width = width, clamped_height = height, clamped_dpi = dpi, + "Clamped session dimensions to safe range" + ); + } let ( mut conn_params, @@ -377,6 +388,7 @@ impl SessionManager { session_id = %session_id, hostname = %hostname, username = %username, + width, height, dpi, "Creating new RDP session" ); @@ -455,6 +467,7 @@ impl SessionManager { tracing::info!( session_id = %session_id, hostname = %hostname, + width, height, dpi, "Creating new VNC session" ); diff --git a/src/websocket.rs b/src/websocket.rs index c3842eb..5dab038 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -18,10 +18,21 @@ use serde::Deserialize; use serde_json::json; use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; +use std::time::Instant; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio_util::sync::CancellationToken; use uuid::Uuid; +/// Which side terminated the proxy connection. +enum ProxyResult { + /// guacd closed the connection (with optional error). + GuacdEnded(Option), + /// Browser/WebSocket closed the connection (with optional error). + BrowserEnded(Option), + /// Session was cancelled externally. + Cancelled, +} + #[derive(Deserialize)] pub struct WsQuery { pub token: Option, @@ -163,12 +174,52 @@ async fn handle_ws( }; // Run the bidirectional proxy - let result = proxy_ws_guacd(ws, guacd_stream, recording_file, cancel).await; + let start = Instant::now(); + let proxy_result = proxy_ws_guacd(ws, guacd_stream, recording_file, cancel).await; + let elapsed = start.elapsed(); manager.disconnect_viewer(session_id).await; - if let Err(e) = result { - tracing::warn!(session_id = %session_id, client_ip = %client_addr, error = %e, "WebSocket proxy ended with error"); + // Log termination direction and timing + let mark_error = match &proxy_result { + ProxyResult::GuacdEnded(err) => { + if elapsed.as_secs() < 5 { + tracing::warn!( + session_id = %session_id, client_ip = %client_addr, + elapsed_ms = elapsed.as_millis() as u64, + error = ?err, + "guacd closed connection quickly (possible connection failure)" + ); + true // mark as error + } else { + tracing::info!( + session_id = %session_id, client_ip = %client_addr, + elapsed_secs = elapsed.as_secs(), + "Proxy ended: guacd closed connection" + ); + false + } + } + ProxyResult::BrowserEnded(err) => { + tracing::info!( + session_id = %session_id, client_ip = %client_addr, + elapsed_secs = elapsed.as_secs(), + error = ?err, + "Proxy ended: browser disconnected" + ); + false + } + ProxyResult::Cancelled => { + tracing::info!( + session_id = %session_id, client_ip = %client_addr, + elapsed_secs = elapsed.as_secs(), + "Proxy ended: session cancelled" + ); + false + } + }; + + if mark_error { manager.error_session(session_id).await; } else { // Only mark completed if no more active connections @@ -203,7 +254,7 @@ async fn proxy_ws_guacd( guacd: GuacdStream, recording_file: Option, cancel: CancellationToken, -) -> Result<(), Box> { +) -> ProxyResult { let (guacd_read, guacd_write) = tokio::io::split(guacd); let (ws_write, ws_read) = ws.split(); @@ -220,21 +271,25 @@ async fn proxy_ws_guacd( // Wait for either direction to finish, or cancellation tokio::select! { result = guacd_to_browser => { - if let Ok(Err(e)) = result { - tracing::debug!("guacd→browser ended: {}", e); - } + let err = match result { + Ok(Err(e)) => Some(e.to_string()), + Err(e) => Some(e.to_string()), + _ => None, + }; + ProxyResult::GuacdEnded(err) } result = browser_to_guacd => { - if let Ok(Err(e)) = result { - tracing::debug!("browser→guacd ended: {}", e); - } + let err = match result { + Ok(Err(e)) => Some(e.to_string()), + Err(e) => Some(e.to_string()), + _ => None, + }; + ProxyResult::BrowserEnded(err) } _ = cancel.cancelled() => { - tracing::info!("Session cancelled, shutting down proxy"); + ProxyResult::Cancelled } } - - Ok(()) } /// Forward data from guacd to WebSocket, recording along the way. diff --git a/static/addressbook.html b/static/addressbook.html index c093a3f..ba236ae 100644 --- a/static/addressbook.html +++ b/static/addressbook.html @@ -4,7 +4,7 @@ rustguac - Address Book @@ -240,7 +253,7 @@ Docs - logout +
Logout
@@ -529,16 +542,15 @@