Skip to content

Starry latest upstream (push) #57

Starry latest upstream (push)

Starry latest upstream (push) #57

Workflow file for this run

name: Starry upstream release
run-name: Starry ${{ inputs.upstream_ref || 'latest upstream' }} (${{ github.event_name }})
on:
push:
branches:
- main
schedule:
- cron: "17 20 * * *"
workflow_dispatch:
inputs:
upstream_ref:
description: "Official rustdesk-server release tag; blank uses the latest formal release"
required: false
type: string
publish:
description: "Publish release assets and GHCR image after every check succeeds"
required: false
default: false
type: boolean
force:
description: "Rebuild even if this Starry release tag already exists"
required: false
default: false
type: boolean
concurrency:
group: starry-upstream-release
cancel-in-progress: false
permissions:
contents: read
env:
CARGO_AUDIT_VERSION: 0.22.2
CARGO_TERM_COLOR: always
CROSS_REVISION: 88f49ff79e777bef6d3564531636ee4d3cc2f8d2
DEBIAN_TEST_IMAGE: debian:bookworm-slim@sha256:abd67ffcfa541b485a3dff59865ab629aa048a6c613e639d36e7456b0b229241
IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/rustdesk-server-starry
RUST_TOOLCHAIN: 1.97.1
RUSTSEC_ADVISORY_DB_REVISION: 2f08fbb85332687b721f2f22706d07448369451b
jobs:
resolve:
name: Resolve upstream and Starry versions
runs-on: ubuntu-24.04
outputs:
upstream_ref: ${{ steps.version.outputs.upstream_ref }}
upstream_version: ${{ steps.version.outputs.upstream_version }}
patch_version: ${{ steps.version.outputs.patch_version }}
release_tag: ${{ steps.version.outputs.release_tag }}
deb_version: ${{ steps.version.outputs.deb_version }}
publish: ${{ steps.version.outputs.publish }}
skip: ${{ steps.version.outputs.skip }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Resolve release policy
id: version
env:
GH_TOKEN: ${{ github.token }}
REQUESTED_REF: ${{ inputs.upstream_ref }}
REQUESTED_PUBLISH: ${{ inputs.publish }}
FORCE_BUILD: ${{ inputs.force }}
AUTO_ENABLED: ${{ vars.STARRY_RELEASE_ENABLED }}
run: |
set -euo pipefail
upstream_ref="${REQUESTED_REF:-}"
if [ -z "$upstream_ref" ]; then
upstream_ref="$(gh api repos/rustdesk/rustdesk-server/releases/latest --jq .tag_name)"
fi
patch_version="$(tr -d '[:space:]' < PATCH_VERSION)"
if ! printf '%s' "$patch_version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "PATCH_VERSION must use X.Y.Z" >&2
exit 1
fi
release_status="$(sed -n 's/^status: //p' RELEASE_STATUS)"
release_status_version="$(sed -n 's/^patch_version: //p' RELEASE_STATUS)"
if [ "$release_status_version" != "$patch_version" ] \
|| ! printf '%s' "$release_status" | grep -Eq '^(BLOCKED|APPROVED)$'; then
echo "RELEASE_STATUS is invalid or does not match PATCH_VERSION" >&2
exit 1
fi
upstream_version="${upstream_ref#v}"
if ! printf '%s' "$upstream_version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([._-][A-Za-z0-9.-]+)?$'; then
echo "Automatic releases require a version-like upstream ref: $upstream_ref" >&2
exit 1
fi
release_tag="${upstream_version}-patch-v${patch_version}"
deb_upstream="$(printf '%s' "$upstream_version" | tr '-' '~')"
deb_version="${deb_upstream}+patch.${patch_version}-1"
publish=false
if [ "${GITHUB_EVENT_NAME}" = schedule ] && [ "${AUTO_ENABLED:-false}" = true ]; then
publish=true
elif [ "${GITHUB_EVENT_NAME}" = workflow_dispatch ] && [ "${REQUESTED_PUBLISH:-false}" = true ]; then
publish=true
fi
if [ "$publish" = true ] && [ "$release_status" != APPROVED ]; then
echo "Publication is blocked by RELEASE_STATUS" >&2
exit 1
fi
skip=false
if [ "${GITHUB_EVENT_NAME}" = schedule ] && [ "${AUTO_ENABLED:-false}" != true ]; then
skip=true
elif [ "${GITHUB_EVENT_NAME}" != push ] && [ "${FORCE_BUILD:-false}" != true ] \
&& gh release view "$release_tag" >/dev/null 2>&1; then
skip=true
fi
{
echo "upstream_ref=$upstream_ref"
echo "upstream_version=$upstream_version"
echo "patch_version=$patch_version"
echo "release_tag=$release_tag"
echo "deb_version=$deb_version"
echo "publish=$publish"
echo "skip=$skip"
} >> "$GITHUB_OUTPUT"
{
echo "## Starry release resolution"
echo
echo "- Upstream: \`rustdesk/rustdesk-server@$upstream_ref\`"
echo "- Starry tag: \`$release_tag\`"
echo "- Publish after validation: \`$publish\`"
echo "- Release gate: \`$release_status\`"
echo "- Skip: \`$skip\`"
} >> "$GITHUB_STEP_SUMMARY"
source:
name: Apply and verify the overlay
needs: resolve
if: needs.resolve.outputs.skip != 'true'
runs-on: ubuntu-24.04
timeout-minutes: 30
outputs:
upstream_commit: ${{ steps.upstream.outputs.upstream_commit }}
upstream_common_commit: ${{ steps.upstream.outputs.upstream_common_commit }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Validate the published Compose examples
run: |
set -euo pipefail
docker compose \
--env-file examples/.env.example \
-f examples/compose.yaml \
config --quiet
docker compose \
--env-file examples/center/.env.example \
-f examples/center/compose.bootstrap.yaml \
config --quiet
docker compose \
--env-file examples/center/.env.example \
-f examples/center/compose.yaml \
config --quiet
docker compose \
--env-file examples/relay/.env.example \
-f examples/relay/compose.yaml \
config --quiet
docker compose \
--env-file examples/control-agent/.env.example \
-f examples/control-agent/compose.yaml \
config --quiet
- name: Validate bilingual documentation and local links
run: |
python3 scripts/check_docs.py
python3 -m unittest discover -s scripts -p 'test_docs.py'
- name: Validate versioned contracts and least-privilege API surface
run: python3 scripts/check_contracts.py
- name: Validate immutable workflow inputs
run: python3 scripts/check_workflows.py
- name: Fetch the exact official source
id: upstream
env:
UPSTREAM_REF: ${{ needs.resolve.outputs.upstream_ref }}
run: |
set -euo pipefail
git init _upstream
git -C _upstream remote add origin https://github.com/rustdesk/rustdesk-server.git
git -C _upstream fetch --depth 1 origin \
"refs/tags/${UPSTREAM_REF}:refs/tags/${UPSTREAM_REF}"
upstream_commit="$(git -C _upstream rev-parse "${UPSTREAM_REF}^{commit}")"
git -C _upstream checkout --detach "$upstream_commit"
git -C _upstream submodule update --init --recursive --depth 1
upstream_common_commit="$(git -C _upstream/libs/hbb_common rev-parse HEAD)"
{
echo "upstream_commit=$upstream_commit"
echo "upstream_common_commit=$upstream_common_commit"
} >> "$GITHUB_OUTPUT"
- name: Apply the Starry overlay twice
run: |
set -euo pipefail
python3 scripts/apply_overlay.py _upstream
first_digest="$({
find _upstream/src _upstream/tests _upstream/contracts \
_upstream/libs/hbb_common/protos _upstream/PATCH_VERSION \
_upstream/libs/hbb_common/src _upstream/Cargo.toml \
_upstream/Cargo.lock _upstream/Cross.toml \
_upstream/libs/hbb_common/Cargo.toml \
-type f ! -name version.rs -print0 \
| LC_ALL=C sort -z \
| xargs -0 sha256sum
} | sha256sum | cut -d' ' -f1)"
python3 scripts/apply_overlay.py _upstream
second_digest="$({
find _upstream/src _upstream/tests _upstream/contracts \
_upstream/libs/hbb_common/protos _upstream/PATCH_VERSION \
_upstream/libs/hbb_common/src _upstream/Cargo.toml \
_upstream/Cargo.lock _upstream/Cross.toml \
_upstream/libs/hbb_common/Cargo.toml \
-type f ! -name version.rs -print0 \
| LC_ALL=C sort -z \
| xargs -0 sha256sum
} | sha256sum | cut -d' ' -f1)"
test "$first_digest" = "$second_digest"
python3 -m py_compile scripts/apply_overlay.py scripts/check_workflows.py
git -C _upstream diff --check
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Verify the fixed patched dependency graph
run: |
set -euo pipefail
cmp overlay/Cargo.lock _upstream/Cargo.lock
cargo metadata --manifest-path _upstream/Cargo.toml \
--format-version 1 --locked >/dev/null
cmp overlay/Cargo.lock _upstream/Cargo.lock
- name: Archive patched source deterministically
run: >-
tar --exclude=.git --sort=name --mtime='UTC 1970-01-01'
--owner=0 --group=0 --numeric-owner -C _upstream -cf - .
| gzip -n > patched-source.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: patched-source
path: patched-source.tar.gz
if-no-files-found: error
retention-days: 3
compression-level: 0
overwrite: true
security-evidence:
name: Secret scan, workflow lint, and source SBOM
needs: resolve
if: needs.resolve.outputs.skip != 'true'
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
persist-credentials: false
- name: Install checksum-pinned security tools
run: sh scripts/install_ci_tools.sh "${RUNNER_TEMP}/starry-tools"
- name: Lint workflow syntax
run: |
"${RUNNER_TEMP}/starry-tools/actionlint"
- name: Scan full Git history for secrets
run: >-
"${RUNNER_TEMP}/starry-tools/gitleaks" git . --config .gitleaks.toml
--redact --no-banner --exit-code 1
- name: Generate source SBOM
run: >-
"${RUNNER_TEMP}/starry-tools/syft" scan dir:.
-o spdx-json=starry-source.spdx.json
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: starry-source-sbom-${{ github.sha }}
path: starry-source.spdx.json
if-no-files-found: error
retention-days: 7
overwrite: true
test:
name: Protocol, rules, fallback, and binary checks
needs:
- resolve
- source
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: patched-source
- name: Extract source
run: mkdir _upstream && tar -xzf patched-source.tar.gz -C _upstream
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
components: rustfmt
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
repository: RustSec/advisory-db
ref: ${{ env.RUSTSEC_ADVISORY_DB_REVISION }}
path: _rustsec-advisory-db
fetch-depth: 1
persist-credentials: false
- name: Audit the fixed Rust dependency graph
run: |
set -euo pipefail
cargo install cargo-audit --version "${CARGO_AUDIT_VERSION}" --locked
test "$(cargo-audit --version)" = "cargo-audit ${CARGO_AUDIT_VERSION}"
cargo audit --db _rustsec-advisory-db --no-fetch \
--file _upstream/Cargo.lock --deny unsound --json \
| tee starry-rustsec-audit.json
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: starry-rustsec-audit-${{ github.sha }}
path: starry-rustsec-audit.json
if-no-files-found: error
retention-days: 7
overwrite: true
- name: Verify overlay formatting
run: |
rustfmt --edition 2021 --check \
_upstream/src/starry_config.rs \
_upstream/src/allocation_explain.rs \
_upstream/src/connection_auth.rs \
_upstream/src/control_agent.rs \
_upstream/src/control_agent/auth.rs \
_upstream/src/control_agent/config_store.rs \
_upstream/src/control_agent/local_client.rs \
_upstream/src/bin/starry-control-agent.rs \
_upstream/src/database.rs \
_upstream/src/fast_relay.rs \
_upstream/src/profile_activation.rs \
_upstream/src/secure_tcp.rs \
_upstream/src/geo_relay.rs \
_upstream/src/geo_relay/rules.rs \
_upstream/src/local_control.rs \
_upstream/src/relay_observer.rs \
_upstream/src/relay_quality.rs \
_upstream/src/websocket_signal.rs \
_upstream/src/websocket_signal/session.rs \
_upstream/src/websocket_signal/routing.rs \
_upstream/src/websocket_signal/relay_health.rs \
_upstream/tests/mixed_relay.rs \
_upstream/tests/local_control.rs \
_upstream/tests/connection_auth_transport.rs \
_upstream/tests/control_agent.rs \
_upstream/tests/protocol_contract.rs \
_upstream/tests/websocket_signal.rs
- name: Run all library tests serially
run: cargo test --manifest-path _upstream/Cargo.toml --locked --lib -j 1
- name: Check all server binaries serially
run: cargo check --manifest-path _upstream/Cargo.toml --locked --bins -j 1
- name: Lock authentication protobuf and client-compatible denial fixtures
run: >-
cargo test --manifest-path _upstream/Cargo.toml --locked
--test protocol_contract -j 1
- name: Exercise HBBS WebSocket registration and cross-transport signalling
run: >-
cargo test --manifest-path _upstream/Cargo.toml --locked
--test websocket_signal -j 1 -- --nocapture
- name: Hold 1,000 registered WebSocket sessions and verify Ping/Pong
run: |
set -euo pipefail
ulimit -n 8192
cargo test --manifest-path _upstream/Cargo.toml --locked \
--test websocket_signal -j 1 \
hbbs_sustains_one_thousand_registered_idle_websockets -- \
--ignored --exact --nocapture --test-threads=1
- name: Lock the official mixed WebSocket and native Relay contract
run: >-
cargo test --manifest-path _upstream/Cargo.toml --locked
--test mixed_relay -j 1 -- --nocapture
- name: Enforce connection JWT across every signalling transport
run: >-
cargo test --manifest-path _upstream/Cargo.toml --locked
--test connection_auth_transport -j 1 -- --nocapture
- name: Exercise bounded loopback-only local control
run: >-
cargo test --manifest-path _upstream/Cargo.toml --locked
--test local_control -j 1 -- --nocapture
- name: Exercise mTLS Control Agent and atomic configuration transactions
run: >-
cargo test --manifest-path _upstream/Cargo.toml --locked
--test control_agent -j 1 -- --nocapture
linux:
name: Linux amd64 static binaries
needs:
- resolve
- source
- security-evidence
- test
runs-on: ubuntu-24.04
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
target: x86_64-unknown-linux-musl
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: patched-source
- name: Extract source
run: mkdir _upstream && tar -xzf patched-source.tar.gz -C _upstream
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
targets: ${{ matrix.target }}
- name: Install the exact cross compiler driver
run: >-
cargo install cross
--git https://github.com/cross-rs/cross
--rev "${CROSS_REVISION}"
--locked
- name: Cross-compile static binaries
working-directory: _upstream
run: >-
cross build --target "${{ matrix.target }}"
--locked --release --bins
- name: Collect Linux release assets
env:
ARCH: ${{ matrix.arch }}
TARGET: ${{ matrix.target }}
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
set -euo pipefail
mkdir artifact
for binary in hbbs hbbr rustdesk-utils starry-control-agent; do
install -m 0755 "_upstream/target/${TARGET}/release/${binary}" \
"artifact/${binary}-${RELEASE_TAG}-linux-${ARCH}"
done
tar --sort=name --mtime='UTC 1970-01-01' \
--owner=0 --group=0 --numeric-owner \
-C "_upstream/target/${TARGET}/release" -cf - \
hbbs hbbr rustdesk-utils starry-control-agent \
| gzip -n > \
"artifact/rustdesk-server-starry-${RELEASE_TAG}-linux-${ARCH}.tar.gz"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: linux-${{ matrix.arch }}
path: artifact/
if-no-files-found: error
retention-days: 3
compression-level: 0
overwrite: true
windows:
name: Experimental Windows amd64 compatibility
needs:
- resolve
- source
- test
runs-on: windows-2025
continue-on-error: true
timeout-minutes: 90
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: patched-source
- name: Extract source
shell: pwsh
run: |
New-Item -ItemType Directory -Path _upstream | Out-Null
tar -xzf patched-source.tar.gz -C _upstream
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Build Windows binaries
run: cargo build --manifest-path _upstream/Cargo.toml --locked --release --bins
- name: Collect Windows release assets
shell: pwsh
env:
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
New-Item -ItemType Directory -Path artifact | Out-Null
# The writable Control Agent is supported and release-tested on Linux.
foreach ($binary in @('hbbs', 'hbbr', 'rustdesk-utils')) {
Copy-Item -LiteralPath "_upstream/target/release/$binary.exe" -Destination "artifact/$binary-$env:RELEASE_TAG-windows-amd64.exe"
}
Compress-Archive -Path artifact/*.exe -DestinationPath "artifact/rustdesk-server-starry-$env:RELEASE_TAG-windows-amd64.zip"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: windows-amd64
path: artifact/
if-no-files-found: error
retention-days: 3
compression-level: 0
overwrite: true
deb:
name: Debian ${{ matrix.arch }} packages
needs:
- resolve
- linux
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
arch:
- amd64
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: linux-${{ matrix.arch }}
path: input
- name: Build independent DEB packages
env:
ARCH: ${{ matrix.arch }}
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
DEB_VERSION: ${{ needs.resolve.outputs.deb_version }}
run: |
set -euo pipefail
chmod +x scripts/build_deb.sh
scripts/build_deb.sh hbbs "input/hbbs-${RELEASE_TAG}-linux-${ARCH}" \
"$ARCH" "$DEB_VERSION" artifact
scripts/build_deb.sh hbbr "input/hbbr-${RELEASE_TAG}-linux-${ARCH}" \
"$ARCH" "$DEB_VERSION" artifact
scripts/build_deb.sh utils "input/rustdesk-utils-${RELEASE_TAG}-linux-${ARCH}" \
"$ARCH" "$DEB_VERSION" artifact
scripts/build_deb.sh agent \
"input/starry-control-agent-${RELEASE_TAG}-linux-${ARCH}" \
"$ARCH" "$DEB_VERSION" artifact
for package in artifact/*.deb; do
dpkg-deb --info "$package" >/dev/null
dpkg-deb --contents "$package" >/dev/null
done
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: deb-${{ matrix.arch }}
path: artifact/*.deb
if-no-files-found: error
retention-days: 3
compression-level: 0
overwrite: true
image-test:
name: Docker image smoke test ${{ matrix.arch }}
needs:
- resolve
- linux
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
arch:
- amd64
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: linux-${{ matrix.arch }}
path: input
- name: Prepare Docker context
env:
ARCH: ${{ matrix.arch }}
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
set -euo pipefail
mkdir -p "docker/${ARCH}"
install -m 0755 "input/hbbs-${RELEASE_TAG}-linux-${ARCH}" "docker/${ARCH}/hbbs"
install -m 0755 "input/hbbr-${RELEASE_TAG}-linux-${ARCH}" "docker/${ARCH}/hbbr"
install -m 0755 "input/rustdesk-utils-${RELEASE_TAG}-linux-${ARCH}" \
"docker/${ARCH}/rustdesk-utils"
install -m 0755 \
"input/starry-control-agent-${RELEASE_TAG}-linux-${ARCH}" \
"docker/${ARCH}/starry-control-agent"
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
with:
driver-opts: image=moby/buildkit@sha256:28a898719c18a33f4e8000685287fa36fd0dd9560c6440227d3a732d79bb41d8
- name: Build the architecture image
env:
ARCH: ${{ matrix.arch }}
run: |
docker buildx build --load --platform "linux/${ARCH}" \
--build-arg "TARGETARCH=${ARCH}" \
--tag "starry-smoke:${ARCH}" docker
- name: Smoke-test all commands
env:
ARCH: ${{ matrix.arch }}
run: |
set -euo pipefail
docker run --rm --platform "linux/${ARCH}" "starry-smoke:${ARCH}" hbbs --help
docker run --rm --platform "linux/${ARCH}" "starry-smoke:${ARCH}" hbbr --help
docker run --rm --platform "linux/${ARCH}" "starry-smoke:${ARCH}" \
rustdesk-utils genkeypair >/dev/null
docker run --rm --platform "linux/${ARCH}" "starry-smoke:${ARCH}" \
starry-control-agent /does-not-exist >/dev/null 2>&1 || test "$?" -ne 127
state_dir="$(mktemp -d)"
container="$(docker run -d --platform "linux/${ARCH}" \
-v "${state_dir}:/root" "starry-smoke:${ARCH}" \
hbbs --starry-config=/root/starry/config.yaml)"
cleanup() {
docker rm -f "$container" >/dev/null 2>&1 || true
}
trap cleanup EXIT
for _ in $(seq 1 20); do
if [ -f "${state_dir}/starry/config.yaml" ] \
&& [ -f "${state_dir}/starry/config.example.yaml" ]; then
break
fi
if [ "$(docker inspect --format '{{.State.Running}}' "$container")" != true ]; then
echo "HBBS stopped before generating the external configuration" >&2
docker logs "$container" >&2 || true
exit 1
fi
sleep 1
done
test -f "${state_dir}/starry/config.yaml"
test ! -s "${state_dir}/starry/config.yaml"
cmp "${state_dir}/starry/config.example.yaml" config/config.example.yaml
cleanup
trap - EXIT
package-test:
name: Debian package install and runtime ${{ matrix.arch }}
needs:
- resolve
- deb
runs-on: ubuntu-24.04
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
arch:
- amd64
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: deb-${{ matrix.arch }}
path: packages
- name: Install packages and exercise every command
env:
ARCH: ${{ matrix.arch }}
run: |
set -euo pipefail
docker run --rm --platform "linux/${ARCH}" \
-v "${PWD}/packages:/packages:ro" \
"${DEBIAN_TEST_IMAGE}" sh -euxc '
printf "#!/bin/sh\nexit 101\n" > /usr/sbin/policy-rc.d
chmod 0755 /usr/sbin/policy-rc.d
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y /packages/*.deb
dpkg-query -W \
rustdesk-server-starry-hbbs \
rustdesk-server-starry-hbbr \
rustdesk-server-starry-utils \
rustdesk-server-starry-control-agent
test "$(id -un rustdesk-starry)" = rustdesk-starry
test "$(stat -c %a /etc/rustdesk-server-starry/managed/config.yaml)" = 640
test "$(stat -c %a /etc/rustdesk-server-starry/control-agent.yaml)" = 640
/usr/bin/hbbs --help
/usr/bin/hbbr --help
/usr/bin/rustdesk-utils genkeypair >/dev/null
/usr/bin/starry-control-agent /does-not-exist >/dev/null 2>&1 || test "$?" -ne 127
'
release-candidate:
name: Assemble auditable release candidate
needs:
- resolve
- source
- security-evidence
- test
- linux
- deb
- image-test
- package-test
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: linux-amd64
path: _artifacts/linux-amd64
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: deb-amd64
path: _artifacts/deb
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: starry-source-sbom-${{ github.sha }}
path: _artifacts/source-sbom
- name: Prepare image context and final release files
env:
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
PATCH_VERSION: ${{ needs.resolve.outputs.patch_version }}
UPSTREAM_REF: ${{ needs.resolve.outputs.upstream_ref }}
UPSTREAM_COMMIT: ${{ needs.source.outputs.upstream_commit }}
UPSTREAM_COMMON_COMMIT: ${{ needs.source.outputs.upstream_common_commit }}
run: |
set -euo pipefail
mkdir -p candidate/docker/amd64 candidate/release-assets
cp docker/Dockerfile candidate/docker/Dockerfile
for arch in amd64; do
install -m 0755 "_artifacts/linux-${arch}/hbbs-${RELEASE_TAG}-linux-${arch}" \
"candidate/docker/${arch}/hbbs"
install -m 0755 "_artifacts/linux-${arch}/hbbr-${RELEASE_TAG}-linux-${arch}" \
"candidate/docker/${arch}/hbbr"
install -m 0755 \
"_artifacts/linux-${arch}/rustdesk-utils-${RELEASE_TAG}-linux-${arch}" \
"candidate/docker/${arch}/rustdesk-utils"
install -m 0755 \
"_artifacts/linux-${arch}/starry-control-agent-${RELEASE_TAG}-linux-${arch}" \
"candidate/docker/${arch}/starry-control-agent"
done
cp _artifacts/linux-amd64/* candidate/release-assets/
cp _artifacts/deb/* candidate/release-assets/
cp _artifacts/source-sbom/starry-source.spdx.json \
candidate/release-assets/
cp config/config.example.yaml candidate/release-assets/config.example.yaml
cp examples/compose.yaml candidate/release-assets/compose.yaml
cp examples/.env.example candidate/release-assets/compose.env.example
python3 scripts/export_docs.py release \
--output candidate/release-assets \
--ref "$GITHUB_SHA" --repository "$GITHUB_REPOSITORY"
tar --sort=name --mtime='UTC 1970-01-01' \
--owner=0 --group=0 --numeric-owner -cf - \
examples config docs/examples \
| gzip -n > \
"candidate/release-assets/deployment-examples-${RELEASE_TAG}.tar.gz"
cat > candidate/release-assets/BUILD-INPUTS.txt <<EOF
repository=${GITHUB_REPOSITORY}
source_commit=${GITHUB_SHA}
upstream_repository=rustdesk/rustdesk-server
upstream_ref=${UPSTREAM_REF}
upstream_commit=${UPSTREAM_COMMIT}
upstream_hbb_common_commit=${UPSTREAM_COMMON_COMMIT}
release_tag=${RELEASE_TAG}
rust_toolchain=${RUST_TOOLCHAIN}
cross_revision=${CROSS_REVISION}
EOF
- name: Generate final-filesystem SBOM and checksums
run: |
set -euo pipefail
sh scripts/install_ci_tools.sh "${RUNNER_TEMP}/starry-tools"
"${RUNNER_TEMP}/starry-tools/syft" scan \
dir:candidate/release-assets \
-o spdx-json=candidate/starry-release.spdx.json
mv candidate/starry-release.spdx.json candidate/release-assets/
(
cd candidate/release-assets
checksums="$(mktemp)"
find . -maxdepth 1 -type f ! -name SHA256SUMS \
-printf '%f\0' | LC_ALL=C sort -z | xargs -0 sha256sum -- \
> "$checksums"
mv "$checksums" SHA256SUMS
sha256sum --check SHA256SUMS
)
- name: Verify candidate contents and Docker context
env:
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
set -euo pipefail
test -s candidate/release-assets/SHA256SUMS
test -s candidate/release-assets/starry-release.spdx.json
test -s candidate/release-assets/starry-source.spdx.json
test "$(find candidate/release-assets -maxdepth 1 -name '*.deb' | wc -l)" -eq 4
test "$(find candidate/release-assets -maxdepth 1 -name '*.tar.gz' | wc -l)" -eq 2
test "$(find candidate/release-assets -maxdepth 1 -name '*.zip' | wc -l)" -eq 0
for arch in amd64; do
for binary in hbbs hbbr rustdesk-utils starry-control-agent; do
test -x "candidate/docker/${arch}/${binary}"
done
done
! find candidate -type f \
\( -name '*.key' -o -name '*.pem' -o -name '*.p12' \) -print -quit \
| grep -q .
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: starry-release-candidate-${{ needs.resolve.outputs.release_tag }}
path: candidate/
if-no-files-found: error
retention-days: 7
compression-level: 0
overwrite: true
publish:
name: Publish release and linux/amd64 image
needs:
- resolve
- release-candidate
if: needs.resolve.outputs.skip != 'true' && needs.resolve.outputs.publish == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 45
permissions:
attestations: write
contents: write
id-token: write
packages: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: starry-release-candidate-${{ needs.resolve.outputs.release_tag }}
path: candidate
- name: Re-verify candidate checksums before signing
working-directory: candidate/release-assets
run: sha256sum --check SHA256SUMS
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
with:
driver-opts: image=moby/buildkit@sha256:28a898719c18a33f4e8000685287fa36fd0dd9560c6440227d3a732d79bb41d8
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Create or verify immutable annotated release tag
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
set -euo pipefail
verify_tag() {
local tag_object_sha
test "$(gh api \
"repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" \
--jq .object.type)" = tag
tag_object_sha="$(gh api \
"repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" \
--jq .object.sha)"
printf '%s' "$tag_object_sha" | grep -Eq '^[0-9a-f]{40}$'
test "$(gh api \
"repos/${GITHUB_REPOSITORY}/git/tags/${tag_object_sha}" \
--jq .tag)" = "$RELEASE_TAG"
test "$(gh api \
"repos/${GITHUB_REPOSITORY}/git/tags/${tag_object_sha}" \
--jq .object.type)" = commit
test "$(gh api \
"repos/${GITHUB_REPOSITORY}/git/tags/${tag_object_sha}" \
--jq .object.sha)" = "$GITHUB_SHA"
}
if gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" \
>/dev/null 2>&1; then
verify_tag
echo "exact annotated tag already exists: $RELEASE_TAG"
exit 0
fi
tag_object_sha="$(gh api --method POST \
"repos/${GITHUB_REPOSITORY}/git/tags" \
-f tag="$RELEASE_TAG" \
-f message="rustdesk-server-starry ${RELEASE_TAG}" \
-f object="$GITHUB_SHA" -f type=commit --jq .sha)"
printf '%s' "$tag_object_sha" | grep -Eq '^[0-9a-f]{40}$'
gh api --method POST "repos/${GITHUB_REPOSITORY}/git/refs" \
-f ref="refs/tags/${RELEASE_TAG}" -f sha="$tag_object_sha" \
--jq .ref | grep -Fx "refs/tags/${RELEASE_TAG}"
verify_tag
- name: Push the final linux/amd64 image
id: final-image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: candidate/docker
platforms: linux/amd64
push: true
provenance: mode=max
sbom: true
tags: |
${{ env.IMAGE_NAME }}:${{ needs.resolve.outputs.release_tag }}
${{ env.IMAGE_NAME }}:latest
labels: |
org.opencontainers.image.title=rustdesk-server-starry
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.url=https://github.com/${{ github.repository }}/releases/tag/${{ needs.resolve.outputs.release_tag }}
org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/blob/${{ github.sha }}/docs/container/CONTAINER.md
org.opencontainers.image.version=${{ needs.resolve.outputs.release_tag }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.licenses=AGPL-3.0-only
org.opencontainers.image.description=patch-v1.3.0 Starry HBBS with connection authentication, GEO Relay quality scoring, safe configuration activation, WebSocket signalling, and an optional mTLS Control Agent; the same image bundles HBBR with compatible Relay data forwarding plus active probes and load telemetry, while account/API services and MMDB data are not included
annotations: |
index:org.opencontainers.image.title=rustdesk-server-starry
index:org.opencontainers.image.source=https://github.com/${{ github.repository }}
index:org.opencontainers.image.url=https://github.com/${{ github.repository }}/releases/tag/${{ needs.resolve.outputs.release_tag }}
index:org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/blob/${{ github.sha }}/docs/container/CONTAINER.md
index:org.opencontainers.image.version=${{ needs.resolve.outputs.release_tag }}
index:org.opencontainers.image.revision=${{ github.sha }}
index:org.opencontainers.image.licenses=AGPL-3.0-only
index:org.opencontainers.image.description=patch-v1.3.0 Starry HBBS with connection authentication, GEO Relay quality scoring, safe configuration activation, WebSocket signalling, and an optional mTLS Control Agent; the same image bundles HBBR with compatible Relay data forwarding plus active probes and load telemetry, while account/API services and MMDB data are not included
- name: Record image and OpenAPI/schema publication summary
env:
PUSHED_DIGEST: ${{ steps.final-image.outputs.digest }}
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
set -euo pipefail
printf '%s' "$PUSHED_DIGEST" | grep -Eq '^sha256:[0-9a-f]{64}$'
image_ref="${IMAGE_NAME}:${RELEASE_TAG}"
registry_digest="$(docker buildx imagetools inspect "$image_ref" \
| sed -n 's/^Digest:[[:space:]]*//p' | head -n 1)"
test "$registry_digest" = "$PUSHED_DIGEST"
manifest_json="${RUNNER_TEMP}/starry-image-index.json"
docker buildx imagetools inspect "${IMAGE_NAME}@${PUSHED_DIGEST}" \
--raw > "$manifest_json"
linux_amd64_count="$(jq '[.manifests[] | select(
.platform.os == "linux" and .platform.architecture == "amd64"
)] | length' "$manifest_json")"
test "$linux_amd64_count" = 1
linux_amd64_digest="$(jq -r '.manifests[] | select(
.platform.os == "linux" and .platform.architecture == "amd64"
) | .digest' "$manifest_json")"
printf '%s' "$linux_amd64_digest" \
| grep -Eq '^sha256:[0-9a-f]{64}$'
upstream_ref="$(sed -n 's/^upstream_ref=//p' \
candidate/release-assets/BUILD-INPUTS.txt)"
upstream_commit="$(sed -n 's/^upstream_commit=//p' \
candidate/release-assets/BUILD-INPUTS.txt)"
upstream_common_commit="$(sed -n \
's/^upstream_hbb_common_commit=//p' \
candidate/release-assets/BUILD-INPUTS.txt)"
python3 scripts/write_release_summary.py \
--output candidate/release-assets/STARRY-RELEASE-SUMMARY.json \
--release-tag "$RELEASE_TAG" \
--source-commit "$GITHUB_SHA" \
--upstream-ref "$upstream_ref" \
--upstream-commit "$upstream_commit" \
--upstream-hbb-common-commit "$upstream_common_commit" \
--image-reference "$image_ref" \
--image-index-digest "$PUSHED_DIGEST" \
--image-linux-amd64-digest "$linux_amd64_digest"
jq -e \
--arg tag "$RELEASE_TAG" \
--arg source "$GITHUB_SHA" \
--arg index "$PUSHED_DIGEST" \
--arg amd64 "$linux_amd64_digest" \
'.release.tag == $tag and .release.source_commit == $source
and .image.index_digest == $index
and .image.platforms["linux/amd64"] == $amd64' \
candidate/release-assets/STARRY-RELEASE-SUMMARY.json >/dev/null
(
cd candidate/release-assets
checksums="$(mktemp)"
find . -maxdepth 1 -type f ! -name SHA256SUMS \
-printf '%f\0' | LC_ALL=C sort -z | xargs -0 sha256sum -- \
> "$checksums"
mv "$checksums" SHA256SUMS
sha256sum --check SHA256SUMS
)
- name: Sign build provenance for every candidate subject
id: provenance
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
with:
subject-checksums: candidate/release-assets/SHA256SUMS
- name: Sign the candidate SBOM assertion
id: sbom-attestation
uses: actions/attest-sbom@4651f806c01d8637787e274ac3bdf724ef169f34 # v3.0.0
with:
subject-checksums: candidate/release-assets/SHA256SUMS
sbom-path: candidate/release-assets/starry-release.spdx.json
- name: Attach portable Sigstore bundles and refresh checksums
env:
PROVENANCE_BUNDLE: ${{ steps.provenance.outputs.bundle-path }}
SBOM_BUNDLE: ${{ steps.sbom-attestation.outputs.bundle-path }}
run: |
set -euo pipefail
cp "$PROVENANCE_BUNDLE" \
candidate/release-assets/BUILD-PROVENANCE.sigstore.json
cp "$SBOM_BUNDLE" \
candidate/release-assets/SBOM-ATTESTATION.sigstore.json
(
cd candidate/release-assets
checksums="$(mktemp)"
find . -maxdepth 1 -type f ! -name SHA256SUMS \
-printf '%f\0' | LC_ALL=C sort -z | xargs -0 sha256sum -- \
> "$checksums"
mv "$checksums" SHA256SUMS
sha256sum --check SHA256SUMS
)
- name: Publish GitHub release assets
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
UPSTREAM_REF: ${{ needs.resolve.outputs.upstream_ref }}
PATCH_VERSION: ${{ needs.resolve.outputs.patch_version }}
run: |
set -euo pipefail
notes_file="candidate/release-assets/RELEASE-NOTES-patch-v${PATCH_VERSION}.md"
chinese_notes="docs/releases/RELEASE-NOTES-patch-v${PATCH_VERSION}.zh-CN.md"
test -f "$notes_file"
test -f "$chinese_notes"
cat > release-notes.md <<EOF
Based on official rustdesk/rustdesk-server ${UPSTREAM_REF}.
Container image: https://github.com/${GITHUB_REPOSITORY}/pkgs/container/rustdesk-server-starry
Recommended Docker deployment: https://github.com/${GITHUB_REPOSITORY}/wiki/Docker-Deployment
Container guide: https://github.com/${GITHUB_REPOSITORY}/blob/${GITHUB_SHA}/docs/container/CONTAINER.md
Single-host Compose asset: https://github.com/${GITHUB_REPOSITORY}/releases/download/${RELEASE_TAG}/compose.yaml
Control Agent sidecar example: https://github.com/${GITHUB_REPOSITORY}/blob/${RELEASE_TAG}/examples/control-agent/compose.yaml
中文发布说明:https://github.com/${GITHUB_REPOSITORY}/blob/${GITHUB_SHA}/${chinese_notes}
Verify binaries, packages, documentation, and the deployment-example
archive against \`SHA256SUMS\` before use.
EOF
cat "$notes_file" >> release-notes.md
if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
gh release upload "$RELEASE_TAG" candidate/release-assets/* --clobber
gh release edit "$RELEASE_TAG" --notes-file release-notes.md --latest
else
gh release create "$RELEASE_TAG" candidate/release-assets/* \
--title "rustdesk-server-starry $RELEASE_TAG" \
--notes-file release-notes.md \
--latest
fi
- name: Write publication summary
env:
PUSHED_DIGEST: ${{ steps.final-image.outputs.digest }}
RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
{
echo "## Starry publication completed"
echo
echo "- Release: \`$RELEASE_TAG\`"
echo "- Image: \`${IMAGE_NAME}:$RELEASE_TAG\`"
echo "- Image index: \`$PUSHED_DIGEST\`"
echo "- Rolling image: \`${IMAGE_NAME}:latest\`"
echo "- Contract digest summary: \`STARRY-RELEASE-SUMMARY.json\`"
} >> "$GITHUB_STEP_SUMMARY"