diff --git a/docker-compose.yml b/docker-compose.yml index fab6e03..c25f5ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,21 +1,69 @@ -version: '3' +# Builds a minimal debian image that installs the hbbs/hbbr/utils .deb from a +# Gitea Actions artifact and runs it. Both services share the same image; only +# the entrypoint command differs. +# +# Settings below are baked in as defaults. To override a value without editing +# this file, export it in your shell or put it in .env, e.g.: +# RUSTDESK_DOMAIN=rd.example.com docker compose up -d +# +# To pin a specific artifact zip (skip API auto-discovery), set ARTIFACT_URL: +# ARTIFACT_URL=https://gitea.cstudio.ch/mike/rustdesk-server/actions/runs/173/artifacts/rustdesk-server-linux-amd64-1e961cdd929f7af97148b76d9de79998a89402a3 \ +# docker compose build +# +# To pick up a newer artifact, bump the cache buster: +# CACHE_BUST=$(date +%s) docker compose build networks: rustdesk-net: external: false +# Build args — passed to docker/Dockerfile.deb. ARTIFACT_URL, if set, short- +# circuits the API discovery and downloads the zip directly. +x-rustdesk-build: &rustdesk-build + context: ./docker + dockerfile: Dockerfile.deb + args: + GITEA_URL: "${GITEA_URL:-https://gitea.cstudio.ch}" + GITEA_OWNER: "${GITEA_OWNER:-mike}" + GITEA_REPO: "${GITEA_REPO:-rustdesk-server}" + GITEA_BRANCH: "${GITEA_BRANCH:-pro-features}" + ARTIFACT_URL: "${ARTIFACT_URL:-}" + CACHE_BUST: "${CACHE_BUST:-0}" + +# Runtime env that hbbs/hbbr actually read. Most settings (relay, bootstrap +# admin, key, http port) are passed via CLI flags below — the binary's +# env-var convention transforms `--foo-bar` into `FOO-BAR` (literal dashes, +# uppercase), which is awkward in YAML, so flags are clearer. The names +# below are the few that the binary reads as plain env vars. +x-rustdesk-env: &rustdesk-env + RUST_LOG: "${RUST_LOG:-info}" + # Force relay for all sessions even on LAN. Uncomment to enable. + # ALWAYS_USE_RELAY: "Y" + # Override DB path. Default: ./db_v2.sqlite3 in WORKDIR + # (= /var/lib/rustdesk-server/db_v2.sqlite3 in this image). + # DB_URL: "/var/lib/rustdesk-server/db_v2.sqlite3" + services: hbbs: container_name: hbbs + build: *rustdesk-build + image: rustdesk-server-cst:latest + platform: linux/amd64 + command: + - hbbs + - --bootstrap-admin-username=${RUSTDESK_BOOTSTRAP_ADMIN_USERNAME:-admin} + - --bootstrap-admin-password=${RUSTDESK_BOOTSTRAP_ADMIN_PASSWORD:-changeme} + # - --key=- # "-" auto-generates a key; "_" forces encrypted-only with no explicit key + # - --http-port=21114 # admin HTTP API/UI port; 0 disables + environment: *rustdesk-env ports: + - 21114:21114 - 21115:21115 - 21116:21116 - 21116:21116/udp - 21118:21118 - image: rustdesk/rustdesk-server:latest - command: hbbs -r rustdesk.example.com:21117 volumes: - - ./data:/root + - ./data:/var/lib/rustdesk-server networks: - rustdesk-net depends_on: @@ -24,13 +72,19 @@ services: hbbr: container_name: hbbr + # Same build + image tag as hbbs — compose builds once and both reuse it. + build: *rustdesk-build + image: rustdesk-server-cst:latest + platform: linux/amd64 + command: + - hbbr + # - --key=- # match the key set on hbbs (if any) + environment: *rustdesk-env ports: - 21117:21117 - 21119:21119 - image: rustdesk/rustdesk-server:latest - command: hbbr volumes: - - ./data:/root + - ./data:/var/lib/rustdesk-server networks: - rustdesk-net restart: unless-stopped diff --git a/docker/Dockerfile.deb b/docker/Dockerfile.deb new file mode 100644 index 0000000..cbea80a --- /dev/null +++ b/docker/Dockerfile.deb @@ -0,0 +1,41 @@ +# Minimal debian image that installs hbbs/hbbr/rustdesk-utils from the .deb +# artifact produced by the Gitea Actions workflow at +# https://gitea.cstudio.ch/mike/rustdesk-server. +# +# To force a fresh artifact pull on rebuild, bump CACHE_BUST (e.g. +# `docker compose build --build-arg CACHE_BUST=$(date +%s)`). +# +# The Gitea workflow only produces amd64 .debs, so pin the image platform. +# On non-amd64 hosts (e.g. Apple Silicon) Docker will emulate via qemu. +FROM --platform=linux/amd64 debian:bookworm-slim + +ARG GITEA_URL=https://gitea.cstudio.ch +ARG GITEA_OWNER=mike +ARG GITEA_REPO=rustdesk-server +ARG GITEA_BRANCH=pro-features +# When set, the script downloads this URL directly and skips API discovery. +ARG ARTIFACT_URL= +ARG CACHE_BUST=0 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates curl jq unzip tini \ + && rm -rf /var/lib/apt/lists/* + +COPY fetch-artifact.sh /usr/local/sbin/fetch-artifact.sh +RUN chmod +x /usr/local/sbin/fetch-artifact.sh + +RUN GITEA_URL="$GITEA_URL" GITEA_OWNER="$GITEA_OWNER" GITEA_REPO="$GITEA_REPO" \ + GITEA_BRANCH="$GITEA_BRANCH" ARTIFACT_URL="$ARTIFACT_URL" \ + /usr/local/sbin/fetch-artifact.sh + +WORKDIR /var/lib/rustdesk-server + +# 21114 admin http, 21115 nat test, 21116/tcp+udp signal, 21117 relay, +# 21118 web socket signal, 21119 web socket relay. +EXPOSE 21114 21115 21116 21116/udp 21117 21118 21119 + +ENTRYPOINT ["/usr/bin/tini", "--"] +CMD ["/usr/bin/hbbs"] diff --git a/docker/fetch-artifact.sh b/docker/fetch-artifact.sh new file mode 100644 index 0000000..69f375c --- /dev/null +++ b/docker/fetch-artifact.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Fetches a Gitea Actions artifact zip and installs every .deb inside. +# +# Two modes: +# 1. $ARTIFACT_URL is set → download that zip directly. +# 2. Otherwise → discover the newest successful run on $GITEA_BRANCH via the +# `/api/v1/.../actions/tasks` endpoint and download +# `/artifacts/`. We use the web +# download URL rather than `/api/v1/.../actions/artifacts`, which on this +# Gitea instance returns an empty list even when uploads have succeeded. +set -euo pipefail + +ARTIFACT_URL="${ARTIFACT_URL:-}" +ARTIFACT_PREFIX="${ARTIFACT_PREFIX:-rustdesk-server-linux-amd64-}" + +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +if [[ -n "$ARTIFACT_URL" ]]; then + zip_url="$ARTIFACT_URL" + echo "==> Using pinned ARTIFACT_URL: $zip_url" +else + : "${GITEA_URL:?GITEA_URL required when ARTIFACT_URL is unset}" + : "${GITEA_OWNER:?GITEA_OWNER required when ARTIFACT_URL is unset}" + : "${GITEA_REPO:?GITEA_REPO required when ARTIFACT_URL is unset}" + : "${GITEA_BRANCH:?GITEA_BRANCH required when ARTIFACT_URL is unset}" + + api="${GITEA_URL%/}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}" + echo "==> Listing workflow runs at $api/actions/tasks (branch=$GITEA_BRANCH)" + list="$(curl -fsSL "$api/actions/tasks?limit=20")" + + # Newest successful run on $GITEA_BRANCH. The .url field is the html run + # page (e.g. .../actions/runs/173) — append /artifacts/ for the zip. + read -r run_url head_sha < <(jq -r --arg branch "$GITEA_BRANCH" ' + .workflow_runs + | map(select(.head_branch == $branch and .status == "success")) + | sort_by(.updated_at) + | last + | if . == null then "" else "\(.url) \(.head_sha)" end + ' <<<"$list") + + if [[ -z "${run_url:-}" || "$run_url" == "null" ]]; then + echo "ERROR: no successful run on branch '$GITEA_BRANCH'." >&2 + jq -r '.workflow_runs[] | " url=\(.url) branch=\(.head_branch) status=\(.status) updated=\(.updated_at)"' <<<"$list" >&2 || true + exit 1 + fi + + zip_url="$run_url/artifacts/${ARTIFACT_PREFIX}${head_sha}" + echo "==> Discovered $zip_url" +fi + +echo "==> Downloading $zip_url" +curl -fsSL -o "$work/artifact.zip" "$zip_url" + +mkdir -p "$work/deb" +unzip -o "$work/artifact.zip" -d "$work/deb" +mapfile -t debs < <(find "$work/deb" -type f -name '*.deb' | sort) +if [[ ${#debs[@]} -eq 0 ]]; then + echo "ERROR: artifact zip contained no .deb files" >&2 + exit 1 +fi +printf ' - %s\n' "${debs[@]}" + +# Postinst scripts call deb-systemd-invoke/systemctl; block them from starting +# anything while we're inside a build layer. +echo '#!/bin/sh' >/usr/sbin/policy-rc.d +echo 'exit 101' >>/usr/sbin/policy-rc.d +chmod +x /usr/sbin/policy-rc.d + +# The .debs declare "Depends: systemd", which would drag full systemd into the +# image. The binaries themselves don't need it at runtime — only the bundled +# .service files reference it — so install with --force-depends. +dpkg -i --force-depends "${debs[@]}" + +rm -f /usr/sbin/policy-rc.d