diff --git a/.context/README.md b/.context/README.md new file mode 100644 index 0000000..7b7cbdc --- /dev/null +++ b/.context/README.md @@ -0,0 +1,29 @@ +# rustdesk-web-stack Context System + +`.context/` is the durable operating memory for humans and coding agents working on this repository. + +## Source precedence + +1. explicit human instruction in the current task; +2. accepted decisions in `.context/decisions/`; +3. project architecture and standards; +4. root and nearest `AGENTS.md`; +5. governing GitHub issue and pull request; +6. latest handoff; +7. session records; +8. temporary notes. + +A session, handoff, issue comment or pull-request discussion does not change architecture by itself. Material decisions must be promoted to an ADR and accepted through human review. + +## Structure + +- `manifest.yaml`: machine-readable precedence, record and release policy. +- `decisions/`: architectural decision records. Accepted records are immutable. +- `sessions/`: chronological implementation/evidence records. +- `handoffs/`: durable continuation notes between people or agents. + +## Required workflow + +Before meaningful implementation: read the manifest and AGENTS rules, identify the governing issue, load relevant accepted decisions, and inspect pinned upstream evidence rather than assuming current defaults. + +After meaningful work: record a session, link the governing issue/PR, list checks performed, record unresolved risks, and create a handoff when the work is not complete. diff --git a/.context/decisions/ADR-0001-stack-packaging-and-deployment.md b/.context/decisions/ADR-0001-stack-packaging-and-deployment.md new file mode 100644 index 0000000..87e78fd --- /dev/null +++ b/.context/decisions/ADR-0001-stack-packaging-and-deployment.md @@ -0,0 +1,31 @@ +# ADR-0001: Stack packaging and deployment model + +Status: Accepted +Date: 2026-08-21 +Governing issue: #9 + +## Context + +The project must ship a reproducible RustDesk browser client together with a maintainable Kubernetes deployment for the self-hosted RustDesk server components. The upstream RustDesk server project provides container images and historical/WIP Helm work, but no current maintained full-stack Helm chart. The web client build path also requires project-owned packaging. + +## Decision + +1. Release Please owns one semantic version for this repository's stack artifacts. +2. A release publishes: + - `ghcr.io/nomed/rustdesk-web-stack/web:`; + - `oci://ghcr.io/nomed/rustdesk-web-stack/charts/rustdesk-stack:`; + - GitHub tag/release `v`. +3. The Helm chart is named `rustdesk-stack` and deploys the complete stack: web client, `hbbs`, `hbbr`, persistence/secrets and networking. +4. RustDesk server image/version is configured and pinned independently from the stack version. +5. The web runtime uses Caddy rather than NGINX. +6. Kubernetes Gateway API is the preferred HTTP/WSS exposure contract. The chart does not install or require a specific Gateway controller; Envoy Gateway and Traefik are reference implementations. +7. Native RustDesk TCP/UDP endpoints remain independently exposable through Kubernetes Services; TCPRoute/UDPRoute may be added optionally when controller support is appropriate. +8. Upstream `rustdesk/rustdesk-server#399` is reference material only, not a Helm dependency. +9. Upstream source and tooling inputs used for released artifacts must be pinned and reviewable; `latest` is not a release default. + +## Consequences + +- Stack and upstream RustDesk versions evolve independently. +- Cluster operators retain control over GatewayClass/controller choice. +- The chart owns more server deployment logic but avoids depending on an abandoned upstream chart. +- Release validation must lint/template the full stack and publish both OCI artifacts atomically from the same Release Please version. diff --git a/.context/manifest.yaml b/.context/manifest.yaml new file mode 100644 index 0000000..e381572 --- /dev/null +++ b/.context/manifest.yaml @@ -0,0 +1,44 @@ +schema_version: 1 +project: rustdesk-web-stack +context_root: .context + +precedence: + - current_human_instruction + - accepted_decisions + - project_architecture_and_standards + - nearest_agents_md + - governing_issue_and_pull_request + - latest_handoff + - sessions + - temporary_notes + +records: + decisions: + path: .context/decisions + id_pattern: "ADR-{number:04d}" + statuses: [Draft, Proposed, Accepted, Deprecated, Superseded, Rejected, Withdrawn] + immutable_after_acceptance: true + handoffs: + path: .context/handoffs + id_pattern: "HANDOFF-{date}-{sequence:02d}" + authoritative: false + sessions: + path: .context/sessions + id_pattern: "SESSION-{date}-{sequence:02d}" + authoritative: false + +write_policy: + sessions: agents_allowed + handoffs: agents_allowed + decisions: proposal_allowed_human_acceptance_required + accepted_records_rewrite: forbidden + architecture_change_without_record: forbidden + release_or_packaging_change_without_record: forbidden + destructive_operation_without_explicit_approval: forbidden + +release_contract: + version_authority: release_please + tag_pattern: "v{version}" + web_image: "ghcr.io/nomed/rustdesk-web-stack/web:{version}" + helm_chart: "oci://ghcr.io/nomed/rustdesk-web-stack/charts/rustdesk-stack:{version}" + stack_version_independent_from_upstream: true diff --git a/.context/sessions/SESSION-2026-08-21-01.md b/.context/sessions/SESSION-2026-08-21-01.md new file mode 100644 index 0000000..8ff0c4e --- /dev/null +++ b/.context/sessions/SESSION-2026-08-21-01.md @@ -0,0 +1,54 @@ +# SESSION-2026-08-21-01 + +Governing issue: #9 +Branch: `feat/full-rustdesk-stack` + +## Objective + +Implement the approved full-stack packaging/deployment model and introduce durable `.context` / `AGENTS.md` repository governance. + +## Evidence inspected + +- `retex-iconic/rgk` `AGENTS.md`, `.context/manifest.yaml` and `.context/README.md` as the operating-context reference model. +- `rustdesk/rustdesk-server#399` as historical/WIP Helm reference for separate `hbbs`/`hbbr` workloads and official ports. +- RustDesk Server current release/package evidence: 1.1.16 is the latest published release/container; 1.1.17 exists in master but is unreleased. + +## Decisions applied + +ADR-0001 records the accepted contract: + +- one Release Please stack version; +- OCI web image under `ghcr.io/nomed/rustdesk-web-stack/web`; +- OCI chart under `ghcr.io/nomed/rustdesk-web-stack/charts/rustdesk-stack`; +- full `rustdesk-stack` chart with web, hbbs and hbbr; +- Caddy web runtime; +- Gateway API without bundled controller; +- independently pinned RustDesk Server release. + +## Changes + +- added `AGENTS.md` and `.context` operating memory; +- replaced the NGINX runtime with Caddy; +- changed the baseline build to use upstream packaging as a builder only, then build the project-owned Caddy image; +- added `charts/rustdesk-stack` with web/hbbs/hbbr, persistence, Services and optional Gateway/HTTPRoute; +- pinned the default official RustDesk Server image to `ghcr.io/rustdesk/rustdesk-server:1.1.16`; +- changed Release Please chart version target and OCI publication paths; +- updated CI to lint/template the full chart and publish web snapshots under the new prefix; +- removed the old web-only chart. + +## Validation + +Validation is delegated to the pull-request CI: + +- Helm lint; +- Helm template with default values; +- Helm template with Gateway API enabled; +- pinned web baseline build; +- generated artifact checks including Caddy runtime provenance. + +## Open risks / next actions + +- confirm the baseline upstream builder still produces the expected static path before the Caddy repackaging step; +- validate a real browser session through Gateway API to hbbs/hbbr WebSocket ports; +- decide/test production-native exposure patterns (LoadBalancer versus optional TCPRoute/UDPRoute) across Envoy Gateway and Traefik; +- consider digest pinning for the RustDesk Server image after initial functional validation. diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 214b219..f89552b 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -8,6 +8,8 @@ on: - "build/**" - "container/**" - "charts/**" + - ".context/**" + - "AGENTS.md" - ".github/workflows/build-web.yml" - "README.md" pull_request: @@ -15,6 +17,8 @@ on: - "build/**" - "container/**" - "charts/**" + - ".context/**" + - "AGENTS.md" - ".github/workflows/build-web.yml" - "README.md" @@ -40,8 +44,12 @@ jobs: shell: bash run: | set -euo pipefail - helm lint charts/rustdesk-web - helm template rustdesk-web charts/rustdesk-web >/dev/null + helm lint charts/rustdesk-stack + helm template rustdesk charts/rustdesk-stack >/dev/null + helm template rustdesk charts/rustdesk-stack \ + --set gateway.enabled=true \ + --set gateway.gatewayName=public \ + --set gateway.hostname=rustdesk.example.com >/dev/null - name: Load pinned baseline versions id: versions @@ -69,8 +77,8 @@ jobs: test -s dist/web/index.html test -s dist/web/main.dart.js || test -s dist/web/flutter_bootstrap.js test -s dist/web/BUILD-INFO.txt + grep -q '^Runtime: Caddy$' dist/web/BUILD-INFO.txt test -d dist/web/js - find dist/web -maxdepth 2 -type f | sort | head -100 - name: Upload web artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -88,14 +96,15 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Publish baseline image + - name: Publish baseline web snapshot if: github.event_name == 'push' && github.ref == 'refs/heads/main' shell: bash run: | set -euo pipefail short_ref="${{ steps.versions.outputs.web_client_ref }}" short_ref="${short_ref:0:12}" - docker tag rustdesk-web-stack:ci ghcr.io/nomed/rustdesk-web-stack:latest - docker tag rustdesk-web-stack:ci "ghcr.io/nomed/rustdesk-web-stack:web-${short_ref}" - docker push ghcr.io/nomed/rustdesk-web-stack:latest - docker push "ghcr.io/nomed/rustdesk-web-stack:web-${short_ref}" + base='ghcr.io/nomed/rustdesk-web-stack/web' + docker tag rustdesk-web-stack:ci "${base}:edge" + docker tag rustdesk-web-stack:ci "${base}:web-${short_ref}" + docker push "${base}:edge" + docker push "${base}:web-${short_ref}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6d893e..b06f617 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,22 +46,23 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build release image + - name: Build release web image shell: bash env: - IMAGE_TAG: ghcr.io/nomed/rustdesk-web-stack:${{ needs.release-please.outputs.version }} + IMAGE_TAG: ghcr.io/nomed/rustdesk-web-stack/web:${{ needs.release-please.outputs.version }} run: bash build/build-baseline-image.sh - - name: Publish release image + - name: Publish release web image shell: bash run: | set -euo pipefail version='${{ needs.release-please.outputs.version }}' - docker push "ghcr.io/nomed/rustdesk-web-stack:${version}" - docker tag "ghcr.io/nomed/rustdesk-web-stack:${version}" "ghcr.io/nomed/rustdesk-web-stack:v${version}" - docker tag "ghcr.io/nomed/rustdesk-web-stack:${version}" ghcr.io/nomed/rustdesk-web-stack:latest - docker push "ghcr.io/nomed/rustdesk-web-stack:v${version}" - docker push ghcr.io/nomed/rustdesk-web-stack:latest + base='ghcr.io/nomed/rustdesk-web-stack/web' + docker push "${base}:${version}" + docker tag "${base}:${version}" "${base}:v${version}" + docker tag "${base}:${version}" "${base}:latest" + docker push "${base}:v${version}" + docker push "${base}:latest" - name: Set up Helm uses: azure/setup-helm@v4.3.1 @@ -70,18 +71,22 @@ jobs: shell: bash run: | set -euo pipefail - helm lint charts/rustdesk-web - helm template rustdesk-web charts/rustdesk-web >/dev/null + helm lint charts/rustdesk-stack + helm template rustdesk charts/rustdesk-stack >/dev/null + helm template rustdesk charts/rustdesk-stack \ + --set gateway.enabled=true \ + --set gateway.gatewayName=public \ + --set gateway.hostname=rustdesk.example.com >/dev/null - name: Package Helm chart shell: bash run: | set -euo pipefail version='${{ needs.release-please.outputs.version }}' - test "$(awk '/^version:/ {print $2}' charts/rustdesk-web/Chart.yaml)" = "${version}" - test "$(awk -F'\"' '/^appVersion:/ {print $2}' charts/rustdesk-web/Chart.yaml)" = "${version}" + test "$(awk '/^version:/ {print $2}' charts/rustdesk-stack/Chart.yaml)" = "${version}" + test "$(awk -F'\"' '/^appVersion:/ {print $2}' charts/rustdesk-stack/Chart.yaml)" = "${version}" mkdir -p dist/chart - helm package charts/rustdesk-web --destination dist/chart + helm package charts/rustdesk-stack --destination dist/chart - name: Log in to GHCR for Helm shell: bash @@ -93,4 +98,5 @@ jobs: run: | set -euo pipefail version='${{ needs.release-please.outputs.version }}' - helm push "dist/chart/rustdesk-web-${version}.tgz" oci://ghcr.io/nomed/charts + helm push "dist/chart/rustdesk-stack-${version}.tgz" \ + oci://ghcr.io/nomed/rustdesk-web-stack/charts diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4e44ff8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,48 @@ +# rustdesk-web-stack Agent Operating Rules + +These instructions apply to humans and coding agents working in this repository. + +## Before acting + +1. Read `.context/manifest.yaml` and `.context/README.md`. +2. Identify the governing GitHub issue and linked pull request. +3. Load accepted decisions relevant to the task. +4. When continuing previous work, read the latest relevant handoff/session. +5. Surface conflicts, missing evidence and upstream uncertainty instead of guessing. + +## Source precedence + +When sources conflict, follow `.context/manifest.yaml`. Current explicit human instruction and accepted architectural decisions outrank repository guidance, issue discussion, sessions and temporary notes. + +## Required workflow + +- Every meaningful change must be governed by a GitHub issue. +- Work on a focused branch and pull request; do not implement directly on `main`. +- Record material architecture, deployment, security, compatibility or release/packaging decisions as an ADR before or with implementation. +- Accepted ADRs are immutable; supersede them with a new record. +- Pin upstream source/image/tooling versions used for released artifacts. Do not silently track `latest` or an upstream default branch. +- Record meaningful implementation work in `.context/sessions/` and create a handoff when continuation is expected. +- Conventional commits are required because Release Please owns stack versioning. + +## Project boundaries + +- This repository packages and deploys RustDesk-derived components; it is not an official RustDesk project. +- Keep upstream source code out of this repository whenever practical. Prefer pinned upstream references plus reproducible build/packaging logic. +- The stack version is independent from RustDesk upstream versions. +- `rustdesk-stack` is the deployment product: web client, `hbbs`, `hbbr`, networking, persistence and secrets. +- The Helm chart must not install or depend on a specific ingress/gateway controller. Kubernetes Gateway API is the preferred L7 contract. +- Native RustDesk TCP/UDP exposure must remain usable independently from the HTTP/WSS gateway path. + +## Release contract + +A Release Please release publishes the same stack version for: + +- `ghcr.io/nomed/rustdesk-web-stack/web:`; +- `oci://ghcr.io/nomed/rustdesk-web-stack/charts/rustdesk-stack:`; +- GitHub tag/release `v`. + +The chart pins/configures its RustDesk server version separately from the stack version. + +## Completion evidence + +At the end of meaningful work record: governing issue/PR, files changed, tests/checks, upstream evidence, decisions applied, unresolved risks and next actions. \ No newline at end of file diff --git a/README.md b/README.md index b182aeb..7827185 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,88 @@ # rustdesk-web-stack -Unofficial, reproducible packaging and deployment stack for the RustDesk Web Client and the RustDesk self-hosted server components required to use it. +Unofficial, reproducible packaging and deployment stack for the RustDesk Web Client and RustDesk self-hosted server components. > [!IMPORTANT] -> This project is **not affiliated with, sponsored by, or endorsed by RustDesk**. RustDesk is developed upstream at https://github.com/rustdesk/rustdesk. +> This project is **not affiliated with, sponsored by, or endorsed by RustDesk**. -## Status +## What this project ships -**Early bootstrap / proof of concept.** - -RustDesk still actively maintains browser-specific application code, and its main GitHub Actions workflow still contains a `build-rustdesk-web` job. However, that job is currently disabled (`if: False`) and the current OSS tree no longer ships the `flutter/web` scaffold that the disabled job assumes is present. - -This repository keeps the current-source reconstruction as an experimental track and uses a pinned, known web-capable RustDesk baseline for the deployable OCI artifacts. - -## Release artifacts - -Releases are managed by Release Please and use one semantic version across the repository, container image and Helm chart. - -For a release such as `v0.1.0` the workflow publishes: +Release Please owns one semantic version for the stack. A release publishes: ```text -ghcr.io/nomed/rustdesk-web-stack:0.1.0 -ghcr.io/nomed/rustdesk-web-stack:v0.1.0 -ghcr.io/nomed/rustdesk-web-stack:latest -oci://ghcr.io/nomed/charts/rustdesk-web --version 0.1.0 +ghcr.io/nomed/rustdesk-web-stack/web: +oci://ghcr.io/nomed/rustdesk-web-stack/charts/rustdesk-stack: +GitHub release v ``` +The stack version is independent from the RustDesk upstream server version. + Install the Helm chart directly from GHCR: ```bash -helm install rustdesk-web \ - oci://ghcr.io/nomed/charts/rustdesk-web \ - --version 0.1.0 +helm install rustdesk \ + oci://ghcr.io/nomed/rustdesk-web-stack/charts/rustdesk-stack \ + --version ``` -By default the chart uses the image version matching `Chart.appVersion`. Override `image.tag` only when intentionally testing a different image. - -## Goals - -- build the RustDesk Web Client from pinned, reviewable source inputs; -- package the generated Flutter Web assets as an OCI image; -- publish the Helm chart as an OCI artifact to GHCR; -- expose the web application through a standard HTTP server; -- deploy `hbbs` and `hbbr` with the WebSocket endpoints required by browser clients; -- provide a Kubernetes/Helm deployment path; -- keep upstream RustDesk source code out of this repository whenever possible; -- make upstream version bumps explicit and reproducible. - -## Non-goals - -- fork or rebrand RustDesk; -- replace the RustDesk protocol or server; -- claim compatibility beyond what is verified by CI and the project test matrix. - -## Planned architecture +## Stack architecture ```text Browser | | HTTPS / WSS v -Ingress / reverse proxy - |-----------------------| - | | - v v -rustdesk-web hbbs / hbbr -(static Flutter app) WebSocket endpoints - | - v - RustDesk remote agent +Kubernetes Gateway API + |-- / -> web (Caddy + Flutter assets) + |-- /ws/id -> hbbs :21118 + `-- /ws/relay -> hbbr :21119 + +Native RustDesk clients + |-- TCP/UDP 21116 -> hbbs + `-- TCP 21117 -> hbbr ``` -## Repository layout +The Helm chart does **not** install a Gateway controller. Envoy Gateway and Traefik are reference implementations; any conformant Gateway API controller may be used. -```text -build/ Web Client build tooling -container/ Runtime container for generated web assets -charts/rustdesk-web/ Helm chart -examples/ Example values/configuration -docs/ Architecture and operational notes -.github/workflows/ CI, Release Please and publication automation -``` +## Helm chart + +`charts/rustdesk-stack` deploys: + +- the project-owned web image; +- `hbbs` and its persistent data; +- `hbbr`; +- services for native and WebSocket ports; +- optional Gateway/HTTPRoute resources; +- optional shared server key injection through an existing Kubernetes Secret. -## Build tracks +The default server image is pinned to the latest verified released RustDesk Server version rather than an upstream `latest` tag. -`build/build-baseline-image.sh` is the release path. Its inputs are pinned in `build/baseline.env` and it produces the web runtime image plus `dist/web`. +## Web build -`build/build-web.sh` is the experimental current-RustDesk reconstruction. Its inputs are pinned in `build/upstream.env`; it restores the historical `flutter/web` scaffold and attempts to regenerate it against current official RustDesk code. +`build/build-baseline-image.sh` uses pinned upstream web-capable sources/tooling to generate the RustDesk Web assets, extracts those assets, and then packages the released runtime with **Caddy**. -## Versioning +`build/build-web.sh` remains the experimental track for reconstructing the Web Client from current official RustDesk sources. -Release Please owns the repository version. Conventional commits drive version bumps and the release PR updates: +## Repository governance + +Read `AGENTS.md` and `.context/README.md` before meaningful work. Material architecture, deployment and release decisions are recorded under `.context/decisions/`; accepted records are immutable and must be superseded rather than edited. + +## Versioning and releases + +Conventional commits drive Release Please. A release PR updates: - `CHANGELOG.md`; - `.release-please-manifest.json`; -- `charts/rustdesk-web/Chart.yaml` (`version` and `appVersion`). - -When the Release Please PR is merged, the same release workflow creates the GitHub release and publishes both the versioned container image and the Helm chart to GHCR. +- `charts/rustdesk-stack/Chart.yaml` (`version` and `appVersion`). -A release PR must retain Release Please's `autorelease: pending` metadata. If a PR has to be recreated manually from the generated release branch, restore that label and preserve the generated Release Please body/header before merging it; otherwise Release Please may treat the merge as an ordinary commit and generate the next release proposal instead of tagging the merged version. +When the Release Please PR is merged, the release workflow creates the GitHub release and publishes both OCI artifacts using the same stack version. -For recovery of a merged-but-untagged release PR, restore `autorelease: pending`, repair the generated body if needed, and re-run the Release Please workflow before accepting a newer release proposal. +A recreated Release Please PR must preserve the generated body and `autorelease: pending` metadata or the merge may not be recognized as a release. -## Roadmap +## Upstream relationship -1. Validate the pinned Web Client baseline and OCI release pipeline. -2. Validate a real browser-to-agent session against self-hosted `hbbs`/`hbbr`. -3. Complete Helm templates for the web client plus `hbbs`/`hbbr` services. -4. Add HTTPS/WSS ingress examples and an end-to-end browser connection test. -5. Continue the current-RustDesk reconstruction until it can replace the baseline release source. +RustDesk server containers are consumed from official upstream releases. The historical upstream Helm PR `rustdesk/rustdesk-server#399` is treated as reference material only; this project maintains its own full-stack chart. -## License and upstream components +## License -This repository contains deployment/build tooling. RustDesk and any upstream artifacts retain their respective licenses and copyright notices. Before redistributing generated RustDesk artifacts, review the applicable upstream license and notices. +This repository contains deployment/build tooling and packages RustDesk-derived artifacts. RustDesk and upstream components retain their respective licenses and copyright notices. diff --git a/build/build-baseline-image.sh b/build/build-baseline-image.sh index 76c68e3..6ba578c 100644 --- a/build/build-baseline-image.sh +++ b/build/build-baseline-image.sh @@ -7,6 +7,7 @@ source "${ROOT_DIR}/build/baseline.env" WORK_DIR="${WORK_DIR:-${ROOT_DIR}/.work}" PACKAGING_DIR="${WORK_DIR}/docker-rustdesk-web-client" +BUILDER_TAG="${BUILDER_TAG:-rustdesk-web-stack-builder:ci}" IMAGE_TAG="${IMAGE_TAG:-rustdesk-web-stack:ci}" OUT_DIR="${OUT_DIR:-${ROOT_DIR}/dist/web}" @@ -31,7 +32,7 @@ actual_packaging_ref="$(git -C "${PACKAGING_DIR}" rev-parse HEAD)" exit 2 } -echo "==> Build RustDesk Web baseline image" +echo "==> Build pinned upstream web builder image" docker build \ --file "${PACKAGING_DIR}/Dockerfile" \ --build-arg "RUSTDESK_REPO=${WEB_CLIENT_REPOSITORY}" \ @@ -39,11 +40,11 @@ docker build \ --build-arg "FLUTTER_VERSION=${FLUTTER_VERSION}" \ --build-arg "RUST_VERSION=${RUST_VERSION}" \ --build-arg "ENABLE_WSS=${ENABLE_WSS}" \ - --tag "${IMAGE_TAG}" \ + --tag "${BUILDER_TAG}" \ "${PACKAGING_DIR}" echo "==> Extract static web artifact" -container_id="$(docker create "${IMAGE_TAG}")" +container_id="$(docker create "${BUILDER_TAG}")" trap 'docker rm -f "${container_id}" >/dev/null 2>&1 || true' EXIT docker cp "${container_id}:/usr/share/nginx/html/." "${OUT_DIR}/" docker rm "${container_id}" >/dev/null @@ -59,7 +60,14 @@ Web client ref: ${WEB_CLIENT_REF} Flutter version: ${FLUTTER_VERSION} Rust version: ${RUST_VERSION} WSS patch enabled: ${ENABLE_WSS} +Runtime: Caddy EOF +echo "==> Build project-owned Caddy runtime image" +docker build \ + --file "${ROOT_DIR}/container/Dockerfile" \ + --tag "${IMAGE_TAG}" \ + "${ROOT_DIR}" + echo "==> Image: ${IMAGE_TAG}" echo "==> Web assets: ${OUT_DIR}" diff --git a/charts/rustdesk-web/Chart.yaml b/charts/rustdesk-stack/Chart.yaml similarity index 67% rename from charts/rustdesk-web/Chart.yaml rename to charts/rustdesk-stack/Chart.yaml index 4eb456a..deb0533 100644 --- a/charts/rustdesk-web/Chart.yaml +++ b/charts/rustdesk-stack/Chart.yaml @@ -1,12 +1,13 @@ apiVersion: v2 -name: rustdesk-web +name: rustdesk-stack version: 0.2.0 # x-release-please-version appVersion: "0.2.0" # x-release-please-version -description: Unofficial Helm chart for the RustDesk Web Client deployment stack +description: Unofficial full-stack Helm chart for RustDesk Web Client, hbbs and hbbr type: application home: https://github.com/nomed/rustdesk-web-stack sources: - https://github.com/nomed/rustdesk-web-stack - https://github.com/rustdesk/rustdesk + - https://github.com/rustdesk/rustdesk-server annotations: artifacthub.io/license: AGPL-3.0 diff --git a/charts/rustdesk-stack/templates/_helpers.tpl b/charts/rustdesk-stack/templates/_helpers.tpl new file mode 100644 index 0000000..d34975e --- /dev/null +++ b/charts/rustdesk-stack/templates/_helpers.tpl @@ -0,0 +1,19 @@ +{{- define "rustdesk-stack.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "rustdesk-stack.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name (include "rustdesk-stack.name" .) | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{- define "rustdesk-stack.labels" -}} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | quote }} +app.kubernetes.io/name: {{ include "rustdesk-stack.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} diff --git a/charts/rustdesk-stack/templates/gateway.yaml b/charts/rustdesk-stack/templates/gateway.yaml new file mode 100644 index 0000000..d4d5ae6 --- /dev/null +++ b/charts/rustdesk-stack/templates/gateway.yaml @@ -0,0 +1,67 @@ +{{- if .Values.gateway.enabled }} +{{- if .Values.gateway.createGateway }} +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: {{ .Values.gateway.gatewayName }} + {{- if .Values.gateway.gatewayNamespace }} + namespace: {{ .Values.gateway.gatewayNamespace }} + {{- end }} +spec: + gatewayClassName: {{ required "gateway.gatewayClassName is required when gateway.createGateway=true" .Values.gateway.gatewayClassName }} + listeners: + - name: {{ .Values.gateway.sectionName }} + protocol: HTTPS + port: 443 + hostname: {{ .Values.gateway.hostname | quote }} + tls: + mode: Terminate + certificateRefs: + - kind: Secret + name: {{ required "gateway.tlsSecretName is required when gateway.createGateway=true" .Values.gateway.tlsSecretName }} +--- +{{- end }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "rustdesk-stack.fullname" . }} +spec: + parentRefs: + - name: {{ .Values.gateway.gatewayName }} + {{- if .Values.gateway.gatewayNamespace }} + namespace: {{ .Values.gateway.gatewayNamespace }} + {{- end }} + sectionName: {{ .Values.gateway.sectionName }} + hostnames: + - {{ .Values.gateway.hostname | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: /ws/id + filters: + - type: URLRewrite + urlRewrite: + path: + type: ReplacePrefixMatch + replacePrefixMatch: / + backendRefs: + - name: {{ include "rustdesk-stack.fullname" . }}-hbbs + port: {{ .Values.hbbs.service.ports.websocket }} + - matches: + - path: + type: PathPrefix + value: /ws/relay + filters: + - type: URLRewrite + urlRewrite: + path: + type: ReplacePrefixMatch + replacePrefixMatch: / + backendRefs: + - name: {{ include "rustdesk-stack.fullname" . }}-hbbr + port: {{ .Values.hbbr.service.ports.websocket }} + - backendRefs: + - name: {{ include "rustdesk-stack.fullname" . }}-web + port: {{ .Values.web.service.port }} +{{- end }} diff --git a/charts/rustdesk-stack/templates/hbbr.yaml b/charts/rustdesk-stack/templates/hbbr.yaml new file mode 100644 index 0000000..a79404e --- /dev/null +++ b/charts/rustdesk-stack/templates/hbbr.yaml @@ -0,0 +1,73 @@ +{{- if .Values.hbbr.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "rustdesk-stack.fullname" . }}-hbbr + labels: + {{- include "rustdesk-stack.labels" . | nindent 4 }} + app.kubernetes.io/component: hbbr +spec: + replicas: {{ .Values.hbbr.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "rustdesk-stack.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: hbbr + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "rustdesk-stack.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: hbbr + spec: + containers: + - name: hbbr + image: "{{ .Values.rustdeskServer.image.repository }}:{{ .Values.rustdeskServer.image.tag }}" + imagePullPolicy: {{ .Values.rustdeskServer.image.pullPolicy }} + command: ["hbbr"] + {{- with .Values.hbbr.extraArgs }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.rustdeskServer.keys.existingSecret }} + envFrom: + - secretRef: + name: {{ .Values.rustdeskServer.keys.existingSecret }} + {{- end }} + ports: + - name: relay + containerPort: 21117 + protocol: TCP + - name: websocket + containerPort: 21119 + protocol: TCP + resources: + {{- toYaml .Values.resources | nindent 12 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "rustdesk-stack.fullname" . }}-hbbr + labels: + {{- include "rustdesk-stack.labels" . | nindent 4 }} + app.kubernetes.io/component: hbbr + {{- with .Values.hbbr.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.hbbr.service.type }} + ports: + - name: relay + port: {{ .Values.hbbr.service.ports.relay }} + targetPort: relay + protocol: TCP + - name: websocket + port: {{ .Values.hbbr.service.ports.websocket }} + targetPort: websocket + protocol: TCP + selector: + app.kubernetes.io/name: {{ include "rustdesk-stack.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: hbbr +{{- end }} diff --git a/charts/rustdesk-stack/templates/hbbs.yaml b/charts/rustdesk-stack/templates/hbbs.yaml new file mode 100644 index 0000000..6d2896b --- /dev/null +++ b/charts/rustdesk-stack/templates/hbbs.yaml @@ -0,0 +1,118 @@ +{{- if .Values.hbbs.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "rustdesk-stack.fullname" . }}-hbbs + labels: + {{- include "rustdesk-stack.labels" . | nindent 4 }} + app.kubernetes.io/component: hbbs +spec: + replicas: {{ .Values.hbbs.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "rustdesk-stack.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: hbbs + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "rustdesk-stack.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: hbbs + spec: + containers: + - name: hbbs + image: "{{ .Values.rustdeskServer.image.repository }}:{{ .Values.rustdeskServer.image.tag }}" + imagePullPolicy: {{ .Values.rustdeskServer.image.pullPolicy }} + command: ["hbbs"] + args: + {{- if .Values.hbbs.extraArgs }} + {{- toYaml .Values.hbbs.extraArgs | nindent 12 }} + {{- else if .Values.hbbs.relay }} + - "-r" + - {{ .Values.hbbs.relay | quote }} + {{- end }} + {{- if .Values.rustdeskServer.keys.existingSecret }} + envFrom: + - secretRef: + name: {{ .Values.rustdeskServer.keys.existingSecret }} + {{- end }} + ports: + - name: nat-test + containerPort: 21115 + protocol: TCP + - name: rendezvous-tcp + containerPort: 21116 + protocol: TCP + - name: rendezvous-udp + containerPort: 21116 + protocol: UDP + - name: websocket + containerPort: 21118 + protocol: TCP + volumeMounts: + - name: data + mountPath: /root + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + - name: data + {{- if .Values.hbbs.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ include "rustdesk-stack.fullname" . }}-hbbs + {{- else }} + emptyDir: {} + {{- end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "rustdesk-stack.fullname" . }}-hbbs + labels: + {{- include "rustdesk-stack.labels" . | nindent 4 }} + app.kubernetes.io/component: hbbs + {{- with .Values.hbbs.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.hbbs.service.type }} + ports: + - name: nat-test + port: {{ .Values.hbbs.service.ports.natTest }} + targetPort: nat-test + protocol: TCP + - name: rendezvous-tcp + port: {{ .Values.hbbs.service.ports.rendezvous }} + targetPort: rendezvous-tcp + protocol: TCP + - name: rendezvous-udp + port: {{ .Values.hbbs.service.ports.rendezvous }} + targetPort: rendezvous-udp + protocol: UDP + - name: websocket + port: {{ .Values.hbbs.service.ports.websocket }} + targetPort: websocket + protocol: TCP + selector: + app.kubernetes.io/name: {{ include "rustdesk-stack.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: hbbs +{{- if .Values.hbbs.persistence.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "rustdesk-stack.fullname" . }}-hbbs + labels: + {{- include "rustdesk-stack.labels" . | nindent 4 }} +spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: {{ .Values.hbbs.persistence.size }} + {{- if .Values.hbbs.persistence.storageClass }} + storageClassName: {{ .Values.hbbs.persistence.storageClass | quote }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/rustdesk-stack/templates/web.yaml b/charts/rustdesk-stack/templates/web.yaml new file mode 100644 index 0000000..382593a --- /dev/null +++ b/charts/rustdesk-stack/templates/web.yaml @@ -0,0 +1,70 @@ +{{- if .Values.web.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "rustdesk-stack.fullname" . }}-web + labels: + {{- include "rustdesk-stack.labels" . | nindent 4 }} + app.kubernetes.io/component: web +spec: + replicas: {{ .Values.web.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "rustdesk-stack.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: web + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "rustdesk-stack.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: web + spec: + containers: + - name: web + image: "{{ .Values.web.image.repository }}:{{ default .Chart.AppVersion .Values.web.image.tag }}" + imagePullPolicy: {{ .Values.web.image.pullPolicy }} + ports: + - name: http + containerPort: 8080 + readinessProbe: + httpGet: + path: / + port: http + livenessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "rustdesk-stack.fullname" . }}-web + labels: + {{- include "rustdesk-stack.labels" . | nindent 4 }} + app.kubernetes.io/component: web +spec: + type: {{ .Values.web.service.type }} + ports: + - name: http + port: {{ .Values.web.service.port }} + targetPort: http + selector: + app.kubernetes.io/name: {{ include "rustdesk-stack.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: web +{{- end }} diff --git a/charts/rustdesk-stack/values.yaml b/charts/rustdesk-stack/values.yaml new file mode 100644 index 0000000..ef9d3c0 --- /dev/null +++ b/charts/rustdesk-stack/values.yaml @@ -0,0 +1,66 @@ +nameOverride: "" +fullnameOverride: "" + +web: + enabled: true + replicaCount: 1 + image: + repository: ghcr.io/nomed/rustdesk-web-stack/web + tag: "" + pullPolicy: IfNotPresent + service: + type: ClusterIP + port: 8080 + +rustdeskServer: + image: + repository: ghcr.io/rustdesk/rustdesk-server + tag: "1.1.16" + pullPolicy: IfNotPresent + keys: + # Optional Secret injected as environment variables into hbbs/hbbr. + # Use a KEY entry to enforce the same server key on both components. + existingSecret: "" + +hbbs: + enabled: true + replicaCount: 1 + relay: "" + extraArgs: [] + persistence: + enabled: true + size: 1Gi + storageClass: "" + service: + type: ClusterIP + annotations: {} + ports: + natTest: 21115 + rendezvous: 21116 + websocket: 21118 + +hbbr: + enabled: true + replicaCount: 1 + extraArgs: [] + service: + type: ClusterIP + annotations: {} + ports: + relay: 21117 + websocket: 21119 + +gateway: + enabled: false + createGateway: false + gatewayClassName: "" + gatewayName: rustdesk + gatewayNamespace: "" + hostname: rustdesk.example.com + sectionName: https + tlsSecretName: "" + +resources: {} +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/charts/rustdesk-web/templates/deployment.yaml b/charts/rustdesk-web/templates/deployment.yaml deleted file mode 100644 index fb8abd7..0000000 --- a/charts/rustdesk-web/templates/deployment.yaml +++ /dev/null @@ -1,45 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Release.Name }}-web - labels: - app.kubernetes.io/name: {{ .Chart.Name }} - app.kubernetes.io/instance: {{ .Release.Name }} -spec: - replicas: {{ .Values.replicaCount }} - selector: - matchLabels: - app.kubernetes.io/name: {{ .Chart.Name }} - app.kubernetes.io/instance: {{ .Release.Name }} - template: - metadata: - annotations: -{{ toYaml .Values.podAnnotations | indent 8 }} - labels: - app.kubernetes.io/name: {{ .Chart.Name }} - app.kubernetes.io/instance: {{ .Release.Name }} - spec: - containers: - - name: web - image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: http - containerPort: {{ .Values.service.targetPort }} - protocol: TCP - readinessProbe: - httpGet: - path: / - port: http - livenessProbe: - httpGet: - path: / - port: http - resources: -{{ toYaml .Values.resources | indent 12 }} - nodeSelector: -{{ toYaml .Values.nodeSelector | indent 8 }} - tolerations: -{{ toYaml .Values.tolerations | indent 8 }} - affinity: -{{ toYaml .Values.affinity | indent 8 }} diff --git a/charts/rustdesk-web/templates/service.yaml b/charts/rustdesk-web/templates/service.yaml deleted file mode 100644 index 6d6d5d2..0000000 --- a/charts/rustdesk-web/templates/service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-web - labels: - app.kubernetes.io/name: {{ .Chart.Name }} - app.kubernetes.io/instance: {{ .Release.Name }} -spec: - type: {{ .Values.service.type }} - selector: - app.kubernetes.io/name: {{ .Chart.Name }} - app.kubernetes.io/instance: {{ .Release.Name }} - ports: - - name: http - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP diff --git a/charts/rustdesk-web/values.yaml b/charts/rustdesk-web/values.yaml deleted file mode 100644 index b46b733..0000000 --- a/charts/rustdesk-web/values.yaml +++ /dev/null @@ -1,29 +0,0 @@ -replicaCount: 1 - -image: - repository: ghcr.io/nomed/rustdesk-web-stack - tag: "" - pullPolicy: IfNotPresent - -service: - type: ClusterIP - port: 80 - targetPort: 80 - -ingress: - enabled: false - className: "" - annotations: {} - hosts: - - host: rustdesk-web.local - paths: - - path: / - pathType: Prefix - tls: [] - -resources: {} - -podAnnotations: {} -nodeSelector: {} -tolerations: [] -affinity: {} diff --git a/container/Caddyfile b/container/Caddyfile new file mode 100644 index 0000000..c6e8033 --- /dev/null +++ b/container/Caddyfile @@ -0,0 +1,11 @@ +:8080 { + root * /srv + encode zstd gzip + try_files {path} /index.html + file_server + + header { + X-Content-Type-Options nosniff + Referrer-Policy no-referrer + } +} diff --git a/container/Dockerfile b/container/Dockerfile index 82bed7d..ff6f70d 100644 --- a/container/Dockerfile +++ b/container/Dockerfile @@ -1,7 +1,7 @@ -FROM nginx:1.27-alpine +FROM caddy:2.10.2-alpine -COPY container/nginx.conf /etc/nginx/conf.d/default.conf -COPY dist/web/ /usr/share/nginx/html/ +COPY container/Caddyfile /etc/caddy/Caddyfile +COPY dist/web/ /srv/ EXPOSE 8080 diff --git a/container/nginx.conf b/container/nginx.conf deleted file mode 100644 index 1f0030d..0000000 --- a/container/nginx.conf +++ /dev/null @@ -1,17 +0,0 @@ -server { - listen 8080; - server_name _; - - root /usr/share/nginx/html; - index index.html; - - location / { - try_files $uri $uri/ /index.html; - } - - location = /healthz { - access_log off; - add_header Content-Type text/plain; - return 200 'ok\n'; - } -} diff --git a/release-please-config.json b/release-please-config.json index 91d701f..3c6297b 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -12,7 +12,7 @@ "extra-files": [ { "type": "generic", - "path": "charts/rustdesk-web/Chart.yaml" + "path": "charts/rustdesk-stack/Chart.yaml" } ] }