From 8ad3f43d21faf4f5613463bfaa5fa3f2b7bcec37 Mon Sep 17 00:00:00 2001 From: Mike Mueller Date: Thu, 7 May 2026 09:53:29 +0200 Subject: [PATCH] ci(linux): add build workflow + Docker build instructions --- .env.example | 37 +++++++ .gitea/workflows/build.yaml | 136 ++++++++++++++++++++++++++ docker/Dockerfile.source | 48 +++++++++ docs/DOCKER.md | 190 ++++++++++++++++++++++++++++++++++++ 4 files changed, 411 insertions(+) create mode 100644 .env.example create mode 100644 .gitea/workflows/build.yaml create mode 100644 docker/Dockerfile.source create mode 100644 docs/DOCKER.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..80911b2 --- /dev/null +++ b/.env.example @@ -0,0 +1,37 @@ +# Copy to .env and edit. docker-compose reads it automatically. + +# --- Required ---------------------------------------------------------------- + +# Public domain clients connect to. hbbs advertises this as the relay address. +RUSTDESK_DOMAIN=rd.gamecom.ch + +# --- Bootstrap admin --------------------------------------------------------- +# Seeded into the users table on the FIRST startup only (when users is empty). +# Subsequent restarts ignore these — change the password via the admin UI. +# Without these set on first boot, you'll have no way to log in. + +RUSTDESK_BOOTSTRAP_ADMIN_USERNAME=admin +RUSTDESK_BOOTSTRAP_ADMIN_PASSWORD=changeme + +# --- Optional runtime -------------------------------------------------------- + +# Pre-shared key. "-" lets hbbs auto-generate on first run; "_" forces +# encrypted-only mode without an explicit key. +#RUSTDESK_KEY=- + +# HTTP management API / admin UI port (pro-features). Set to 0 to disable. +#RUSTDESK_HTTP_PORT=21114 + +# Force relay for all sessions even on LAN. +#RUSTDESK_ALWAYS_USE_RELAY=Y + +#RUST_LOG=info + +# --- Optional build source --------------------------------------------------- +# Override the upstream repo / branch the image is built from. + +#RUSTDESK_GIT_URL=https://gitea.cstudio.ch/mike/rustdesk-server.git +#RUSTDESK_GIT_BRANCH=pro-features + +# --- Database connectivity --------------------------------------------------- +DATABASE_URL=sqlite://./db_v2.sqlite3 diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..00521d0 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,136 @@ +name: build + +on: + push: + branches: [pro-features] + workflow_dispatch: + inputs: + version_suffix: + description: "Version suffix (e.g. 'cst', 'beta1'). Empty = vanilla." + type: string + default: "cst" + +env: + # Cargo.lock is lockfile v4, which requires Rust >= 1.78. Upstream's + # .github/workflows/build.yaml pins 1.90; mirror that here. + RUST_VERSION: "1.90" + VERSION_BASE: "1.1.15" + VERSION_SUFFIX: ${{ inputs.version_suffix || 'cst' }} + +jobs: + build-amd64: + name: build-linux-amd64 + # Same self-hosted runner as the rustdesk client build. provision.sh on the + # host installs the Rust toolchain and devscripts/debhelper used here. + runs-on: [self-hosted, Linux, X64, ubuntu-22.04] + timeout-minutes: 120 + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Verify host toolchain + shell: bash + run: | + required=(git bash rustc cargo rustup pkg-config debuild dh dpkg-deb) + missing=() + for t in "${required[@]}"; do + if command -v "$t" >/dev/null 2>&1; then + printf '%-20s %s\n' "$t" "$(command -v "$t")" + else + missing+=("$t") + printf '%-20s MISSING\n' "$t" + fi + done + if [[ ${#missing[@]} -gt 0 ]]; then + echo "Missing tools: ${missing[*]}. Install via: sudo apt install -y devscripts build-essential debhelper pkg-config" + exit 1 + fi + + - name: Compute version strings + shell: bash + run: | + base="${VERSION_BASE}" + suffix="${VERSION_SUFFIX}" + [[ "$base" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { + echo "VERSION_BASE '$base' must be major.minor.patch"; exit 1; } + if [[ -n "$suffix" ]]; then display="${base}-${suffix}"; else display="${base}"; fi + echo "Base : $base" + echo "Suffix : $suffix" + echo "Display : $display" + echo "VERSION_DISPLAY=$display" >> "$GITHUB_ENV" + + - name: Patch Cargo.toml with display version + shell: bash + run: | + sed -i -E "0,/^version[[:space:]]*=/{s/^version[[:space:]]*=[[:space:]]*\"${VERSION_BASE}\"/version = \"${VERSION_DISPLAY}\"/}" Cargo.toml + grep '^version' Cargo.toml | head -1 + + - name: Patch debian/changelog with display version + shell: bash + run: | + # The .deb filename is derived from the first changelog entry's + # parenthesized version, NOT from Cargo.toml. Rewrite the leading + # entry so the produced .debs carry $VERSION_DISPLAY. + sed -i -E "0,/^rustdesk-server \(${VERSION_BASE}\)/{s/^rustdesk-server \(${VERSION_BASE}\)/rustdesk-server (${VERSION_DISPLAY})/}" debian/changelog + head -1 debian/changelog + + - name: Ensure Rust toolchain configured + shell: bash + run: | + rustup toolchain install "$RUST_VERSION" --profile minimal --component rustfmt + rustup default "$RUST_VERSION" + rustup target add x86_64-unknown-linux-gnu + rustc --version + cargo --version + + - name: Build hbbs / hbbr / rustdesk-utils + shell: bash + run: | + set -e + # Native build for the runner's amd64 host. --all-features matches + # what upstream's GitHub workflow uses for its musl cross builds. + cargo build --release --all-features + mkdir -p debian-build/amd64/bin + cp -v target/release/hbbs debian-build/amd64/bin/ + cp -v target/release/hbbr debian-build/amd64/bin/ + cp -v target/release/rustdesk-utils debian-build/amd64/bin/ + chmod -v a+x debian-build/amd64/bin/* + + - name: Build .deb packages (amd64) + shell: bash + run: | + set -e + # Mirrors the deb-package job in upstream's .github/workflows/build.yaml: + # stage debian/ and systemd/ next to the pre-built bin/ tree, then run + # debuild -b so dh's auto_build step is a no-op (no source detected) + # and dh_install just packages the binaries listed in the .install files. + cp -vr debian systemd debian-build/amd64/ + sed "s/{{ ARCH }}/amd64/" debian/control.tpl > debian-build/amd64/debian/control + (cd debian-build/amd64 && debuild -i -us -uc -b -aamd64) + + mkdir -p ./SignOutput + mv -v ./debian-build/rustdesk-server-hbbs_${VERSION_DISPLAY}_amd64.deb ./SignOutput/ + mv -v ./debian-build/rustdesk-server-hbbr_${VERSION_DISPLAY}_amd64.deb ./SignOutput/ + mv -v ./debian-build/rustdesk-server-utils_${VERSION_DISPLAY}_amd64.deb ./SignOutput/ + + - name: Report signing status of build artifacts + shell: bash + run: | + # .deb files are typically signed with debsign or via the apt repo + # signing pipeline, not the .deb itself. Just list contents for now. + for f in ./SignOutput/*.deb; do + [[ -f "$f" ]] || continue + size=$(stat -c%s "$f") + printf '[UNSIGNED] %s (%d bytes)\n' "$(basename "$f")" "$size" + done + echo "::warning title=Unsigned .deb::Wire up debsigs / repo signing before distributing." + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: rustdesk-server-linux-amd64-${{ github.sha }} + path: SignOutput/rustdesk-server-*.deb + if-no-files-found: error + retention-days: 14 diff --git a/docker/Dockerfile.source b/docker/Dockerfile.source new file mode 100644 index 0000000..a40af52 --- /dev/null +++ b/docker/Dockerfile.source @@ -0,0 +1,48 @@ +# Multi-stage build: clones rustdesk-server from a Git remote and builds the +# hbbs / hbbr / rustdesk-utils binaries from source. +# +# Build args: +# RUSTDESK_GIT_URL Git URL to clone (default: gitea.cstudio.ch fork) +# RUSTDESK_GIT_BRANCH Branch / tag / ref to check out (default: pro-features) +# RUST_VERSION Rust toolchain image tag (default: 1-bookworm) + +ARG RUST_VERSION=1-bookworm + +FROM rust:${RUST_VERSION} AS builder + +ARG RUSTDESK_GIT_URL=https://gitea.cstudio.ch/mike/rustdesk-server.git +ARG RUSTDESK_GIT_BRANCH=pro-features +# sqlx::query! macros verify SQL at compile time against the checked-in +# db_v2.sqlite3. Override only if you point cargo at a different DB. +ARG DATABASE_URL= + +RUN apt-get update \ + && apt-get install -y --no-install-recommends git ca-certificates pkg-config cmake \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src + +RUN git clone --recurse-submodules --shallow-submodules \ + --branch "${RUSTDESK_GIT_BRANCH}" --single-branch \ + "${RUSTDESK_GIT_URL}" . + +RUN if [ -n "${DATABASE_URL}" ]; then export DATABASE_URL="${DATABASE_URL}"; fi \ + && cargo build --release --bins + +# Runtime stage +FROM debian:bookworm-slim + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates tini \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /src/target/release/hbbs /usr/local/bin/hbbs +COPY --from=builder /src/target/release/hbbr /usr/local/bin/hbbr +COPY --from=builder /src/target/release/rustdesk-utils /usr/local/bin/rustdesk-utils +COPY --from=builder /src/admin_ui /opt/rustdesk/admin_ui + +WORKDIR /root +EXPOSE 21114 21115 21116 21116/udp 21117 21118 21119 + +ENTRYPOINT ["/usr/bin/tini", "--"] +CMD ["hbbs"] diff --git a/docs/DOCKER.md b/docs/DOCKER.md new file mode 100644 index 0000000..c8fab67 --- /dev/null +++ b/docs/DOCKER.md @@ -0,0 +1,190 @@ +# RustDesk Server — Docker Compose Deployment + +The repo ships a `docker-compose.yml` that **builds the server from source** +(this fork's `pro-features` branch) and runs `hbbs` + `hbbr` as two +containers. No prebuilt image is pulled — every `docker compose build` +clones the configured Git URL and runs `cargo build --release` inside the +build stage. + +For the runtime flag reference (CLI options accepted by `hbbs` itself), see +[CONFIGURATION.md](CONFIGURATION.md). This document only covers the Compose +glue. + +--- + +## Quick start + +```bash +cp .env.example .env +# edit .env — at minimum set RUSTDESK_DOMAIN, and change +# RUSTDESK_BOOTSTRAP_ADMIN_PASSWORD before the first boot +docker compose up -d --build +``` + +The bootstrap admin (default `admin` / `changeme`) is seeded into the +`users` table on the **first** boot only — once the row exists, those +flags are ignored. If you boot with the default password and forget to +change it, rotate it via the admin UI; if you forget the password +entirely, delete `./data/db_v2.sqlite3` (loses all server-side state) or +edit the `users` row with `sqlite3` directly. + +First build pulls the Rust toolchain image and compiles the workspace; expect +several minutes. Subsequent builds reuse the cargo cache layer unless the +Git ref or build args change. + +After it boots: + +| Endpoint | Port | Purpose | +|------------------------------------------|---------|------------------------------------------| +| `tcp://:21115` | 21115 | NAT test | +| `tcp+udp://:21116` | 21116 | ID / rendezvous (desktop clients) | +| `tcp://:21117` | 21117 | Relay (hbbr) | +| `ws://:21118` | 21118 | Browser-facing rendezvous WebSocket | +| `ws://:21119` | 21119 | Browser-facing relay WebSocket | +| `http://:21114/admin/` | 21114 | Admin dashboard (pro-features) | +| `http://:21114/api/*` | 21114 | Management API (pro-features) | + +Persistent state — including the auto-generated `id_ed25519` keypair and the +SQLite database — lives in `./data/` (bind-mounted to `/root` in both +containers). + +--- + +## Files + +| File | Role | +|-----------------------------|---------------------------------------------------------------------------------------| +| `docker-compose.yml` | Two services (`hbbs`, `hbbr`) sharing one image built from `docker/Dockerfile.source`.| +| `docker/Dockerfile.source` | Multi-stage build: clones the repo, runs `cargo build --release`, copies binaries into a `debian:bookworm-slim` runtime. | +| `.env.example` | Documented template; copy to `.env`. | +| `data/` | Created on first run. Contains keypair + SQLite DB. **Back this up.** | + +The legacy single-stage `docker/Dockerfile` (busybox + s6-overlay, expects +prebuilt binaries) and `docker-classic/Dockerfile` are unrelated to this +flow and unused by `docker-compose.yml`. + +--- + +## Environment variables + +These are read by `docker-compose.yml` from `.env`. Compose ships them +through to the container as command-line flags or `environment:` entries, +not as raw process env (with the exception of `RUST_LOG` and +`ALWAYS_USE_RELAY`, which `hbbs` reads from env directly). + +### Runtime + +| Variable | Default | Effect | +|--------------------------------|-----------------|-------------------------------------------------------------------------------------------------------| +| `RUSTDESK_DOMAIN` | **required** | Public hostname clients connect to. Passed as `hbbs -r ${RUSTDESK_DOMAIN}:21117`. | +| `RUSTDESK_BOOTSTRAP_ADMIN_USERNAME` | `admin` | Seeded as the initial admin on **first boot only** (when the `users` table is empty). Ignored on subsequent restarts. Empty disables the bootstrap. | +| `RUSTDESK_BOOTSTRAP_ADMIN_PASSWORD` | `changeme` | Same — bcrypt-hashed at insert. Change this in `.env` before the first `up`, or rotate via the admin UI immediately after. | +| `RUSTDESK_KEY` | `-` | Pre-shared key. `-` = auto-generate on first boot (written to `./data/id_ed25519{,.pub}`); `_` = encrypted-only with auto-key; or paste a base64 public key to pin it. Applied to **both** `hbbs` and `hbbr` via `-k`. | +| `RUSTDESK_HTTP_PORT` | `21114` | Pro-features admin API + dashboard port. Set to `0` to disable HTTP entirely. The host port published is the same value. | +| `RUSTDESK_ALWAYS_USE_RELAY` | `N` | Force every session through the relay even on LAN. Read from env by hbbs (any non-empty/non-`N` value enables). | +| `RUST_LOG` | `info` | Log filter. e.g. `debug`, `hbbs=debug,sqlx=warn`. | + +### Build source + +| Variable | Default | Effect | +|------------------------|---------------------------------------------------------------|-----------------------------------------------------------------------------------------| +| `RUSTDESK_GIT_URL` | `https://gitea.cstudio.ch/mike/rustdesk-server.git` | Repo cloned inside the builder stage. | +| `RUSTDESK_GIT_BRANCH` | `pro-features` | Branch / tag / commit to check out (`--branch` so it must be a ref name, not a SHA). | +| `DATABASE_URL` | unset (uses the cloned repo's `.env`) | Overrides the `DATABASE_URL` sqlx reads at compile time. Rarely needed — see below. | + +Build-arg changes only take effect when the image is rebuilt: +`docker compose build --no-cache hbbs` (or `up -d --build`). + +--- + +## Why `DATABASE_URL` is a build-time concern + +`hbbs` uses `sqlx::query!` macros, which verify SQL **at compile time** by +running the queries against a real SQLite database. The repo includes a +checked-in `db_v2.sqlite3` with the schema pre-applied, and a tracked `.env` +file pointing at it (`DATABASE_URL=sqlite://./db_v2.sqlite3`). + +Cargo automatically reads `.env` from the project root, so `cargo build` +inside the builder stage Just Works without any explicit configuration. + +You only need to set `DATABASE_URL` in the Compose `.env` if you fork the +schema or want to point the compile-time check at a different SQLite file. +At **runtime** the binary opens its own DB under `/root/` (your `./data/` +bind mount) — that path is not configurable via this variable. + +--- + +## Adding extra `hbbs` flags + +`docker-compose.yml` only wires the most common flags. To pass others +(SMTP, OIDC, recording dir, audit retention, …), edit the `command:` +block of the `hbbs` service. Example — enable SMTP and set the public +base URL needed for OIDC callbacks: + +```yaml + command: > + hbbs + -r ${RUSTDESK_DOMAIN:?...}:21117 + -k ${RUSTDESK_KEY:--} + --http-port ${RUSTDESK_HTTP_PORT:-21114} + --admin-ui-dir /opt/rustdesk/admin_ui + --bootstrap-admin-username=${RUSTDESK_BOOTSTRAP_ADMIN_USERNAME:-} + --bootstrap-admin-password=${RUSTDESK_BOOTSTRAP_ADMIN_PASSWORD:-} + --public-base-url https://${RUSTDESK_DOMAIN}:${RUSTDESK_HTTP_PORT:-21114} + --smtp-host ${SMTP_HOST} + --smtp-user ${SMTP_USER} + --smtp-pass ${SMTP_PASS} + --smtp-from ${SMTP_FROM} +``` + +Then add the matching variables to `.env`. The full flag list lives in +[CONFIGURATION.md](CONFIGURATION.md). + +If you mount an `oidc.toml`, drop it into `./data/` and pass +`--oidc-config /root/oidc.toml`. + +--- + +## Operational notes + +**Upgrading.** Pull the latest commit on `pro-features` and rebuild: + +```bash +docker compose build --pull --no-cache +docker compose up -d +``` + +The `--pull` refreshes the Rust toolchain base image; `--no-cache` forces a +fresh `git clone` (otherwise Docker will reuse the cached clone layer). +Alternatively, bump `RUSTDESK_GIT_BRANCH` to a tag and rebuild — the changed +build arg invalidates the clone layer automatically. + +**Logs.** `docker compose logs -f hbbs` (or `hbbr`). Both containers run +the binary in the foreground. + +**Persistence.** Everything that matters is in `./data/`: +`id_ed25519{,.pub}` (the server keypair — losing this invalidates every +existing client) and `db_v2.sqlite3` (users, address books, audit, etc.). +Back up the whole directory. + +**TLS.** The server itself speaks plain HTTP on 21114 and plain WebSocket +on 21118 / 21119. Front it with nginx or Caddy for TLS — see the "TLS +deployment" section in `CONFIGURATION.md`. When you do, set +`--http-listen=127.0.0.1` and `--ws-listen=127.0.0.1` in the `command:` +block so the reverse proxy can claim the public ports. + +**Building behind a proxy.** Pass `HTTP_PROXY` / `HTTPS_PROXY` build args +through Compose: + +```yaml + build: + <<: *rustdesk-build + args: + HTTP_PROXY: http://proxy.internal:3128 + HTTPS_PROXY: http://proxy.internal:3128 +``` + +**Resource hint.** Cold compile takes ~3–5 GB of RAM for the linker step +(LTO + `codegen-units = 1`). On a small VPS, build the image on a beefier +machine, push to a registry, and pull from the VPS instead — set the +`image:` field to a registry tag and drop the `build:` block.