From 1df480a59495baef033a4d90891746dabc9d88e6 Mon Sep 17 00:00:00 2001 From: hashbk Date: Fri, 14 Aug 2026 21:31:02 +0800 Subject: [PATCH 1/8] feat: add OpenWrt ipk packaging support for x86_64 and aarch64 Ship prebuilt musl SEA binaries (Node.js + sqlite3 + sharp) as .ipk packages for OpenWrt/ImmortalWrt soft routers, managed via procd. - Add scripts/build-openwrt-ipk.sh to assemble .ipk from SEA dist - Add openwrt/ feed: Makefile (binary package), procd init script, default env config, and README - Extend release.yml and nightly.yml with a musl-build job that builds SEA in node:24-alpine (musl) for linux/amd64 and linux/arm64, packages .ipk, and uploads tarball + ipk to the release - Document OpenWrt deployment in README.md --- .github/workflows/nightly.yml | 57 ++++++++++++++++ .github/workflows/release.yml | 48 +++++++++++++ README.md | 18 +++++ openwrt/Makefile | 57 ++++++++++++++++ openwrt/README.md | 102 ++++++++++++++++++++++++++++ openwrt/files/rustdesk-console.env | 11 +++ openwrt/files/rustdesk-console.init | 42 ++++++++++++ scripts/build-openwrt-ipk.sh | 91 +++++++++++++++++++++++++ 8 files changed, 426 insertions(+) create mode 100644 openwrt/Makefile create mode 100644 openwrt/README.md create mode 100644 openwrt/files/rustdesk-console.env create mode 100644 openwrt/files/rustdesk-console.init create mode 100644 scripts/build-openwrt-ipk.sh diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a5a92458..eec6cf0d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -204,3 +204,60 @@ jobs: rustdesk-console-${{ matrix.target }}.tar.gz rustdesk-console-${{ matrix.target }}.zip fail_on_unmatched_files: false + + musl-build: + name: Build musl SEA + ipk (${{ matrix.arch }}) + needs: nightly + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + platform: linux/amd64 + os: ubuntu-latest + sea_target: linux-x64-musl + - arch: aarch64 + platform: linux/arm64 + os: ubuntu-24.04-arm + sea_target: linux-arm64-musl + runs-on: ${{ matrix.os }} + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Set nightly version in package.json + shell: bash + run: | + VERSION=${{ needs.nightly.outputs.version }} + node -e "const pkg = require('./package.json'); pkg.version = '${VERSION}'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');" + + - name: Build SEA in Alpine (musl) + run: | + docker run --rm --platform ${{ matrix.platform }} \ + -v "$PWD":/work -w /work \ + node:24-alpine sh -c "apk add --no-cache build-base python3 && npm ci && npm run build:sea" + + - name: Build ipk + run: | + VERSION=${{ needs.nightly.outputs.version }} + bash scripts/build-openwrt-ipk.sh dist-sea "$VERSION" ${{ matrix.arch }} . + + - name: Create musl SEA tarball + run: | + cd dist-sea + tar czf ../rustdesk-console-${{ matrix.sea_target }}.tar.gz . + + - name: Upload to release + uses: softprops/action-gh-release@v3 + with: + tag_name: nightly + files: | + rustdesk-console-${{ matrix.sea_target }}.tar.gz + rustdesk-console_${{ needs.nightly.outputs.version }}_${{ matrix.arch }}.ipk + fail_on_unmatched_files: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index da94ffd6..2b407288 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -149,3 +149,51 @@ jobs: rustdesk-console-${{ matrix.target }}.tar.gz rustdesk-console-${{ matrix.target }}.zip fail_on_unmatched_files: false + + musl-build: + name: Build musl SEA + ipk (${{ matrix.arch }}) + needs: release + if: ${{ needs.release.outputs.skipped == 'false' }} + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + platform: linux/amd64 + os: ubuntu-latest + sea_target: linux-x64-musl + - arch: aarch64 + platform: linux/arm64 + os: ubuntu-24.04-arm + sea_target: linux-arm64-musl + runs-on: ${{ matrix.os }} + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + ref: ${{ needs.release.outputs.version }} + + - name: Build SEA in Alpine (musl) + run: | + docker run --rm --platform ${{ matrix.platform }} \ + -v "$PWD":/work -w /work \ + node:24-alpine sh -c "apk add --no-cache build-base python3 && npm ci && npm run build:sea" + + - name: Build ipk + run: | + VERSION=${{ needs.release.outputs.version }} + bash scripts/build-openwrt-ipk.sh dist-sea "$VERSION" ${{ matrix.arch }} . + + - name: Create musl SEA tarball + run: | + cd dist-sea + tar czf ../rustdesk-console-${{ matrix.sea_target }}.tar.gz . + + - name: Upload to release + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ needs.release.outputs.version }} + files: | + rustdesk-console-${{ matrix.sea_target }}.tar.gz + rustdesk-console_${{ needs.release.outputs.version }}_${{ matrix.arch }}.ipk + fail_on_unmatched_files: false diff --git a/README.md b/README.md index 94c62a34..ca7fd885 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,24 @@ npm run start:prod +
+📦 OpenWrt / ImmortalWrt (soft router) + +Prebuilt `.ipk` packages are available for **x86_64** and **aarch64** (musl) +soft routers from [GitHub Releases](https://github.com/databk/rustdesk-console/releases). + +```sh +opkg install rustdesk-console__x86_64.ipk +/etc/init.d/rustdesk-console enable +/etc/init.d/rustdesk-console start +``` + +The backend API runs at `http://:3000/api`. Configure it via +`/etc/rustdesk-console/rustdesk-console.env`. See [`openwrt/README.md`](openwrt/README.md) +for feed integration and details. + +
+ ## 🛠️ Tech Stack `NestJS 11` · `TypeScript` · `TypeORM 0.3` · `SQLite` · `JWT` · `Passport.js` · `bcryptjs` · `otplib` · `Nodemailer` · `sharp` · `openid-client` diff --git a/openwrt/Makefile b/openwrt/Makefile new file mode 100644 index 00000000..a78ffe2b --- /dev/null +++ b/openwrt/Makefile @@ -0,0 +1,57 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=rustdesk-console +PKG_VERSION:=1.8.0 +PKG_RELEASE:=1 + +ifeq ($(ARCH),x86_64) + SEA_ARCH:=x64 +else ifeq ($(ARCH),aarch64) + SEA_ARCH:=arm64 +else + SEA_ARCH:=unsupported +endif + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-linux-$(SEA_ARCH)-musl.tar.gz +PKG_SOURCE_URL:=https://github.com/databk/rustdesk-console/releases/download/$(PKG_VERSION) +PKG_HASH:= +PKG_MIRROR_HASH:= +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) + +include $(INCLUDE_DIR)/package.mk + +define Package/rustdesk-console + SECTION:=net + CATEGORY:=Network + SUBMENU:=RustDesk + TITLE:=RustDesk Console management platform + DEPENDS:=+libc +libstdcpp +libgcc + MAINTAINER:=databk +endef + +define Package/rustdesk-console/description + Enterprise-grade management platform for the RustDesk ecosystem. + Self-hosted console as an alternative to RustDesk Server Pro. + This package ships prebuilt musl binaries (Node.js SEA + sqlite3 + sharp) + for x86_64 and aarch64 only. Running on other architectures will fail. +endef + +Build/Configure:= +Build/Compile:= + +define Package/rustdesk-console/install + $(INSTALL_DIR) $(1)/usr/lib/rustdesk-console + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_DIR) $(1)/etc/rustdesk-console + $(TAR) -C $(PKG_BUILD_DIR) -xzf $(DL_DIR)/$(PKG_SOURCE) + $(INSTALL_BIN) $(PKG_BUILD_DIR)/rustdesk-console $(1)/usr/lib/rustdesk-console/rustdesk-console + $(CP) $(PKG_BUILD_DIR)/templates $(1)/usr/lib/rustdesk-console/templates + $(CP) $(PKG_BUILD_DIR)/node_modules $(1)/usr/lib/rustdesk-console/node_modules + $(INSTALL_BIN) ./files/rustdesk-console.init $(1)/etc/init.d/rustdesk-console + $(INSTALL_DATA) ./files/rustdesk-console.env $(1)/etc/rustdesk-console/rustdesk-console.env + printf '#!/bin/sh\nexec /usr/lib/rustdesk-console/rustdesk-console "$$@"\n' > $(1)/usr/bin/rustdesk-console + chmod 0755 $(1)/usr/bin/rustdesk-console +endef + +$(eval $(call BuildPackage,rustdesk-console)) \ No newline at end of file diff --git a/openwrt/README.md b/openwrt/README.md new file mode 100644 index 00000000..c5d163a4 --- /dev/null +++ b/openwrt/README.md @@ -0,0 +1,102 @@ +# RustDesk Console — OpenWrt Package + +This directory contains the OpenWrt/ImmortalWrt package definition for RustDesk +Console. It ships **prebuilt musl binaries** (Node.js SEA single executable + +`sqlite3` + `sharp` native modules) and wraps them in an `.ipk` package managed +by `procd`. + +## Supported targets + +| Architecture | OpenWrt ARCH | SEA tarball | +|--------------|--------------|-------------| +| x86_64 (soft router) | `x86_64` | `rustdesk-console--linux-x64-musl.tar.gz` | +| aarch64 (ARMv8) | `aarch64` | `rustdesk-console--linux-arm64-musl.tar.gz` | + +Only **musl** libc builds of OpenWrt/ImmortalWrt are supported (the default). +glibc-based OpenWrt builds are not supported. + +## Install a prebuilt .ipk + +Download the `.ipk` for your architecture from the +[GitHub Releases](https://github.com/databk/rustdesk-console/releases) page and: + +```sh +opkg install rustdesk-console__x86_64.ipk +``` + +Then enable and start the service: + +```sh +/etc/init.d/rustdesk-console enable +/etc/init.d/rustdesk-console start +``` + +The backend API is now available at `http://:3000/api`. + +## Configuration + +Edit `/etc/rustdesk-console/rustdesk-console.env` (a conffile, preserved across +upgrades) and restart the service. Key variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `PORT` | `3000` | HTTP listen port | +| `JWT_SECRET` | must be changed | JWT signing secret | +| `DATA_DIR` | `/var/lib/rustdesk-console` | SQLite DB, avatars, nexus builds | + +## Build the package from the OpenWrt feed + +This `Makefile` is a **binary package**: it downloads a prebuilt musl SEA +tarball from GitHub Releases and packages it — it does **not** compile Node.js +inside the OpenWrt buildroot. + +1. Add this directory to your OpenWrt feed (e.g. `feeds.conf`): + ``` + src-link rustdesk_console /path/to/this/openwrt + ``` +2. Update and install the feed: + ```sh + ./scripts/feeds update rustdesk_console + ./scripts/feeds install rustdesk-console + ``` +3. Select your target (`x86_64` or `aarch64`, musl) in `make menuconfig`, then + enable `Network -> RustDesk -> rustdesk-console`. +4. Update `PKG_HASH` in `Makefile` to the sha256 of the downloaded tarball + (required by recent buildroot versions): + ```sh + sha256sum dl/rustdesk-console--linux--musl.tar.gz + ``` +5. Build: + ```sh + make package/rustdesk-console/compile V=s + ``` + The `.ipk` appears in `bin/packages//rustdesk_console/`. + +## Frontend + +This package only installs the **backend API**. Deploy the +[frontend](https://github.com/databk/rustdesk-console-web) separately and point +it at `http://:3000`, e.g. with an external nginx reverse proxy. + +## How the prebuilt tarballs are produced + +The musl SEA tarballs and `.ipk` files are built in CI (see +`.github/workflows/release.yml` and `nightly.yml`): + +1. `node:24-alpine` container runs `npm ci && npm run build:sea` → musl SEA + binary + musl native modules (`sqlite3`, `sharp` use their `linuxmusl` + prebuilds). +2. `scripts/build-openwrt-ipk.sh` assembles the `.ipk` (data + control archive) + with the procd init script and default config. + +## File layout (installed) + +``` +/usr/lib/rustdesk-console/rustdesk-console # SEA executable (musl) +/usr/lib/rustdesk-console/templates/{email,oidc} # email/OIDC templates +/usr/lib/rustdesk-console/node_modules/{sqlite3,sharp} +/usr/bin/rustdesk-console # wrapper -> SEA executable +/etc/init.d/rustdesk-console # procd init script +/etc/rustdesk-console/rustdesk-console.env # config (conffile) +/var/lib/rustdesk-console/ # data dir (created on install) +``` \ No newline at end of file diff --git a/openwrt/files/rustdesk-console.env b/openwrt/files/rustdesk-console.env new file mode 100644 index 00000000..93364d61 --- /dev/null +++ b/openwrt/files/rustdesk-console.env @@ -0,0 +1,11 @@ +# RustDesk Console configuration +# After editing, restart the service: /etc/init.d/rustdesk-console restart + +# HTTP listen port +PORT=3000 + +# JWT secret (CHANGE THIS to a strong random value before production use) +JWT_SECRET=please-change-this-to-a-strong-secret + +# Data directory for the SQLite database, avatars and nexus build artifacts +DATA_DIR=/var/lib/rustdesk-console \ No newline at end of file diff --git a/openwrt/files/rustdesk-console.init b/openwrt/files/rustdesk-console.init new file mode 100644 index 00000000..6be0e153 --- /dev/null +++ b/openwrt/files/rustdesk-console.init @@ -0,0 +1,42 @@ +#!/bin/sh /etc/rc.common + +USE_PROCD=1 +START=99 +STOP=10 + +NAME=rustdesk-console +BINARY=/usr/lib/rustdesk-console/rustdesk-console +ENV_FILE=/etc/rustdesk-console/rustdesk-console.env +DATA_DIR=/var/lib/rustdesk-console + +start_service() { + mkdir -p "$DATA_DIR" + + procd_open_instance + procd_set_param command "$BINARY" + procd_set_param env DATA_DIR="$DATA_DIR" + + if [ -f "$ENV_FILE" ]; then + while IFS='=' read -r key val; do + case "$key" in + ''|\#*|DATA_DIR) continue ;; + esac + val="${val%\"}" + val="${val#\"}" + procd_set_param env "$key=$val" + done < "$ENV_FILE" + fi + + procd_set_param respawn + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +reload_service() { + restart +} + +service_triggers() { + true +} \ No newline at end of file diff --git a/scripts/build-openwrt-ipk.sh b/scripts/build-openwrt-ipk.sh new file mode 100644 index 00000000..e2f242df --- /dev/null +++ b/scripts/build-openwrt-ipk.sh @@ -0,0 +1,91 @@ +#!/bin/bash +set -euo pipefail + +SEA_DIR="${1:-dist-sea}" +VERSION="${2:?usage: build-openwrt-ipk.sh [out_dir]}" +ARCH="${3:?arch required (x86_64|aarch64)}" +OUT_DIR="${4:-.}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +FILES_DIR="$SCRIPT_DIR/../openwrt/files" + +if [ ! -d "$SEA_DIR" ]; then + echo "ERROR: SEA dist directory '$SEA_DIR' not found" >&2 + exit 1 +fi +if [ ! -x "$SEA_DIR/rustdesk-console" ]; then + echo "ERROR: SEA executable '$SEA_DIR/rustdesk-console' not found" >&2 + exit 1 +fi + +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +DATA="$WORK/data" +mkdir -p "$DATA/usr/lib/rustdesk-console" "$DATA/usr/bin" \ + "$DATA/etc/init.d" "$DATA/etc/rustdesk-console" + +cp "$SEA_DIR/rustdesk-console" "$DATA/usr/lib/rustdesk-console/rustdesk-console" +chmod 0755 "$DATA/usr/lib/rustdesk-console/rustdesk-console" + +if [ -d "$SEA_DIR/templates" ]; then + cp -a "$SEA_DIR/templates" "$DATA/usr/lib/rustdesk-console/templates" +fi +if [ -d "$SEA_DIR/node_modules" ]; then + cp -a "$SEA_DIR/node_modules" "$DATA/usr/lib/rustdesk-console/node_modules" +fi + +cat > "$DATA/usr/bin/rustdesk-console" <<'WRAPPER' +#!/bin/sh +exec /usr/lib/rustdesk-console/rustdesk-console "$@" +WRAPPER +chmod 0755 "$DATA/usr/bin/rustdesk-console" + +cp "$FILES_DIR/rustdesk-console.init" "$DATA/etc/init.d/rustdesk-console" +chmod 0755 "$DATA/etc/init.d/rustdesk-console" + +cp "$FILES_DIR/rustdesk-console.env" "$DATA/etc/rustdesk-console/rustdesk-console.env" +chmod 0644 "$DATA/etc/rustdesk-console/rustdesk-console.env" + +CTRL="$WORK/control" +mkdir -p "$CTRL" + +cat > "$CTRL/control" < +Section: net +Priority: optional +Depends: libc, libstdcpp, libgcc +Description: Enterprise-grade management platform for the RustDesk ecosystem. + Self-hosted console as an alternative to RustDesk Server Pro. + Ships prebuilt musl binaries (Node.js SEA + sqlite3 + sharp). +EOF + +cat > "$CTRL/postinst" <<'EOF' +#!/bin/sh +mkdir -p /var/lib/rustdesk-console +exit 0 +EOF +chmod 0755 "$CTRL/postinst" + +cat > "$CTRL/prerm" <<'EOF' +#!/bin/sh +/etc/init.d/rustdesk-console stop 2>/dev/null || true +exit 0 +EOF +chmod 0755 "$CTRL/prerm" + +cat > "$CTRL/conffiles" <<'EOF' +/etc/rustdesk-console/rustdesk-console.env +EOF + +printf '2.0\n' > "$WORK/debian-binary" + +( cd "$DATA" && tar czf "$WORK/data.tar.gz" --owner=0 --group=0 --mtime=@0 . ) +( cd "$CTRL" && tar czf "$WORK/control.tar.gz" --owner=0 --group=0 --mtime=@0 . ) + +OUT="$OUT_DIR/rustdesk-console_${VERSION}_${ARCH}.ipk" +ar rcD "$OUT" "$WORK/debian-binary" "$WORK/control.tar.gz" "$WORK/data.tar.gz" + +echo "Built $OUT ($(du -h "$OUT" | cut -f1))" \ No newline at end of file From 4f1cac4b7b15f3e7c493b791411649f3291b9163 Mon Sep 17 00:00:00 2001 From: hashbk Date: Sat, 22 Aug 2026 22:27:34 +0800 Subject: [PATCH 2/8] feat(openwrt): add .apk packaging support alongside .ipk Add build-openwrt-apk.sh to produce .apk packages (apk-tools 3.x / OpenWrt 24.10+) using concatenated control + data tar.gz streams with .PKGINFO metadata. Both .ipk and .apk are now built and uploaded in release and nightly workflows. --- .github/workflows/nightly.yml | 8 ++- .github/workflows/release.yml | 8 ++- README.md | 17 ++++++- openwrt/README.md | 26 +++++++--- scripts/build-openwrt-apk.sh | 92 +++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 10 deletions(-) create mode 100755 scripts/build-openwrt-apk.sh diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index eec6cf0d..20377636 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -206,7 +206,7 @@ jobs: fail_on_unmatched_files: false musl-build: - name: Build musl SEA + ipk (${{ matrix.arch }}) + name: Build musl SEA + ipk + apk (${{ matrix.arch }}) needs: nightly strategy: fail-fast: false @@ -248,6 +248,11 @@ jobs: VERSION=${{ needs.nightly.outputs.version }} bash scripts/build-openwrt-ipk.sh dist-sea "$VERSION" ${{ matrix.arch }} . + - name: Build apk + run: | + VERSION=${{ needs.nightly.outputs.version }} + bash scripts/build-openwrt-apk.sh dist-sea "$VERSION" ${{ matrix.arch }} . + - name: Create musl SEA tarball run: | cd dist-sea @@ -260,4 +265,5 @@ jobs: files: | rustdesk-console-${{ matrix.sea_target }}.tar.gz rustdesk-console_${{ needs.nightly.outputs.version }}_${{ matrix.arch }}.ipk + rustdesk-console_${{ needs.nightly.outputs.version }}_${{ matrix.arch }}.apk fail_on_unmatched_files: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b407288..99504c26 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -151,7 +151,7 @@ jobs: fail_on_unmatched_files: false musl-build: - name: Build musl SEA + ipk (${{ matrix.arch }}) + name: Build musl SEA + ipk + apk (${{ matrix.arch }}) needs: release if: ${{ needs.release.outputs.skipped == 'false' }} strategy: @@ -184,6 +184,11 @@ jobs: VERSION=${{ needs.release.outputs.version }} bash scripts/build-openwrt-ipk.sh dist-sea "$VERSION" ${{ matrix.arch }} . + - name: Build apk + run: | + VERSION=${{ needs.release.outputs.version }} + bash scripts/build-openwrt-apk.sh dist-sea "$VERSION" ${{ matrix.arch }} . + - name: Create musl SEA tarball run: | cd dist-sea @@ -196,4 +201,5 @@ jobs: files: | rustdesk-console-${{ matrix.sea_target }}.tar.gz rustdesk-console_${{ needs.release.outputs.version }}_${{ matrix.arch }}.ipk + rustdesk-console_${{ needs.release.outputs.version }}_${{ matrix.arch }}.apk fail_on_unmatched_files: false diff --git a/README.md b/README.md index ca7fd885..358f5640 100644 --- a/README.md +++ b/README.md @@ -137,11 +137,24 @@ npm run start:prod
📦 OpenWrt / ImmortalWrt (soft router) -Prebuilt `.ipk` packages are available for **x86_64** and **aarch64** (musl) -soft routers from [GitHub Releases](https://github.com/databk/rustdesk-console/releases). +Prebuilt `.ipk` and `.apk` packages are available for **x86_64** and **aarch64** +(musl) soft routers from [GitHub Releases](https://github.com/databk/rustdesk-console/releases). + +**OpenWrt < 24.10** (opkg / `.ipk`): ```sh opkg install rustdesk-console__x86_64.ipk +``` + +**OpenWrt >= 24.10** (apk / `.apk`): + +```sh +apk add --allow-untrusted rustdesk-console__x86_64.apk +``` + +Then enable and start the service: + +```sh /etc/init.d/rustdesk-console enable /etc/init.d/rustdesk-console start ``` diff --git a/openwrt/README.md b/openwrt/README.md index c5d163a4..14260877 100644 --- a/openwrt/README.md +++ b/openwrt/README.md @@ -2,8 +2,8 @@ This directory contains the OpenWrt/ImmortalWrt package definition for RustDesk Console. It ships **prebuilt musl binaries** (Node.js SEA single executable + -`sqlite3` + `sharp` native modules) and wraps them in an `.ipk` package managed -by `procd`. +`sqlite3` + `sharp` native modules) and wraps them in `.ipk` and `.apk` +packages managed by `procd`. ## Supported targets @@ -15,15 +15,27 @@ by `procd`. Only **musl** libc builds of OpenWrt/ImmortalWrt are supported (the default). glibc-based OpenWrt builds are not supported. -## Install a prebuilt .ipk +## Install a prebuilt package -Download the `.ipk` for your architecture from the -[GitHub Releases](https://github.com/databk/rustdesk-console/releases) page and: +Download the package for your architecture from the +[GitHub Releases](https://github.com/databk/rustdesk-console/releases) page. + +### OpenWrt < 24.10 (opkg / `.ipk`) ```sh opkg install rustdesk-console__x86_64.ipk ``` +### OpenWrt >= 24.10 (apk / `.apk`) + +```sh +apk add --allow-untrusted rustdesk-console__x86_64.apk +``` + +> The `--allow-untrusted` flag is needed because the `.apk` is not signed with +> a build key. For production feeds, sign the package with your own key and +> configure `apk` trust accordingly. + Then enable and start the service: ```sh @@ -80,7 +92,7 @@ it at `http://:3000`, e.g. with an external nginx reverse proxy. ## How the prebuilt tarballs are produced -The musl SEA tarballs and `.ipk` files are built in CI (see +The musl SEA tarballs, `.ipk` and `.apk` files are built in CI (see `.github/workflows/release.yml` and `nightly.yml`): 1. `node:24-alpine` container runs `npm ci && npm run build:sea` → musl SEA @@ -88,6 +100,8 @@ The musl SEA tarballs and `.ipk` files are built in CI (see prebuilds). 2. `scripts/build-openwrt-ipk.sh` assembles the `.ipk` (data + control archive) with the procd init script and default config. +3. `scripts/build-openwrt-apk.sh` assembles the `.apk` (concatenated control + + data tar.gz streams with `.PKGINFO`) for OpenWrt 24.10+ (apk-tools 3.x). ## File layout (installed) diff --git a/scripts/build-openwrt-apk.sh b/scripts/build-openwrt-apk.sh new file mode 100755 index 00000000..0940d0bd --- /dev/null +++ b/scripts/build-openwrt-apk.sh @@ -0,0 +1,92 @@ +#!/bin/bash +set -euo pipefail + +SEA_DIR="${1:-dist-sea}" +VERSION="${2:?usage: build-openwrt-apk.sh [out_dir]}" +ARCH="${3:?arch required (x86_64|aarch64)}" +OUT_DIR="${4:-.}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +FILES_DIR="$SCRIPT_DIR/../openwrt/files" + +if [ ! -d "$SEA_DIR" ]; then + echo "ERROR: SEA dist directory '$SEA_DIR' not found" >&2 + exit 1 +fi +if [ ! -x "$SEA_DIR/rustdesk-console" ]; then + echo "ERROR: SEA executable '$SEA_DIR/rustdesk-console' not found" >&2 + exit 1 +fi + +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +DATA="$WORK/data" +mkdir -p "$DATA/usr/lib/rustdesk-console" "$DATA/usr/bin" \ + "$DATA/etc/init.d" "$DATA/etc/rustdesk-console" + +cp "$SEA_DIR/rustdesk-console" "$DATA/usr/lib/rustdesk-console/rustdesk-console" +chmod 0755 "$DATA/usr/lib/rustdesk-console/rustdesk-console" + +if [ -d "$SEA_DIR/templates" ]; then + cp -a "$SEA_DIR/templates" "$DATA/usr/lib/rustdesk-console/templates" +fi +if [ -d "$SEA_DIR/node_modules" ]; then + cp -a "$SEA_DIR/node_modules" "$DATA/usr/lib/rustdesk-console/node_modules" +fi + +cat > "$DATA/usr/bin/rustdesk-console" <<'WRAPPER' +#!/bin/sh +exec /usr/lib/rustdesk-console/rustdesk-console "$@" +WRAPPER +chmod 0755 "$DATA/usr/bin/rustdesk-console" + +cp "$FILES_DIR/rustdesk-console.init" "$DATA/etc/init.d/rustdesk-console" +chmod 0755 "$DATA/etc/init.d/rustdesk-console" + +cp "$FILES_DIR/rustdesk-console.env" "$DATA/etc/rustdesk-console/rustdesk-console.env" +chmod 0644 "$DATA/etc/rustdesk-console/rustdesk-console.env" + +DATA_SIZE="$(du -sb "$DATA" | cut -f1)" +BUILD_DATE="$(date +%s)" + +CTRL="$WORK/control" +mkdir -p "$CTRL" + +cat > "$CTRL/.PKGINFO" < +size = $DATA_SIZE +arch = $ARCH +origin = rustdesk-console +maintainer = databk +depend = libc +depend = libstdcpp +depend = libgcc +EOF + +cat > "$CTRL/.post-install" <<'EOF' +#!/bin/sh +mkdir -p /var/lib/rustdesk-console +exit 0 +EOF +chmod 0755 "$CTRL/.post-install" + +cat > "$CTRL/.pre-deinstall" <<'EOF' +#!/bin/sh +/etc/init.d/rustdesk-console stop 2>/dev/null || true +exit 0 +EOF +chmod 0755 "$CTRL/.pre-deinstall" + +OUT="$OUT_DIR/rustdesk-console_${VERSION}_${ARCH}.apk" + +( cd "$CTRL" && tar czf "$WORK/control.tar.gz" --owner=0 --group=0 --mtime=@0 . ) +( cd "$DATA" && tar czf "$WORK/data.tar.gz" --owner=0 --group=0 --mtime=@0 . ) + +cat "$WORK/control.tar.gz" "$WORK/data.tar.gz" > "$OUT" + +echo "Built $OUT ($(du -h "$OUT" | cut -f1))" \ No newline at end of file From a350de31852957403650508bbba5998bcec4f273 Mon Sep 17 00:00:00 2001 From: hashbk Date: Thu, 27 Aug 2026 13:09:30 +0800 Subject: [PATCH 3/8] fix(openwrt): use apk mkpkg for v3 ADB format instead of manual tar.gz The previous .apk builder produced concatenated tar.gz streams which is the legacy v2 format. OpenWrt 24.10+ snapshots use apk-tools 3.x which expects the new ADB binary format. Rewrite build-openwrt-apk.sh to use 'apk mkpkg' inside an alpine:edge container, and update the workflows to invoke the script there. Also convert nightly version suffixes (-nightly.) to apk-compatible _p suffixes. --- .github/workflows/nightly.yml | 4 +- .github/workflows/release.yml | 4 +- openwrt/README.md | 4 +- scripts/build-openwrt-apk.sh | 91 +++++++++++++++++------------------ 4 files changed, 52 insertions(+), 51 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 20377636..109e0789 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -251,7 +251,9 @@ jobs: - name: Build apk run: | VERSION=${{ needs.nightly.outputs.version }} - bash scripts/build-openwrt-apk.sh dist-sea "$VERSION" ${{ matrix.arch }} . + docker run --rm --platform ${{ matrix.platform }} \ + -v "$PWD":/work -w /work \ + alpine:edge sh -c "apk add --no-cache bash && bash scripts/build-openwrt-apk.sh dist-sea \"$VERSION\" ${{ matrix.arch }} ." - name: Create musl SEA tarball run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99504c26..9b0ba817 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -187,7 +187,9 @@ jobs: - name: Build apk run: | VERSION=${{ needs.release.outputs.version }} - bash scripts/build-openwrt-apk.sh dist-sea "$VERSION" ${{ matrix.arch }} . + docker run --rm --platform ${{ matrix.platform }} \ + -v "$PWD":/work -w /work \ + alpine:edge sh -c "apk add --no-cache bash && bash scripts/build-openwrt-apk.sh dist-sea \"$VERSION\" ${{ matrix.arch }} ." - name: Create musl SEA tarball run: | diff --git a/openwrt/README.md b/openwrt/README.md index 14260877..fa73a352 100644 --- a/openwrt/README.md +++ b/openwrt/README.md @@ -100,8 +100,8 @@ The musl SEA tarballs, `.ipk` and `.apk` files are built in CI (see prebuilds). 2. `scripts/build-openwrt-ipk.sh` assembles the `.ipk` (data + control archive) with the procd init script and default config. -3. `scripts/build-openwrt-apk.sh` assembles the `.apk` (concatenated control + - data tar.gz streams with `.PKGINFO`) for OpenWrt 24.10+ (apk-tools 3.x). +3. `scripts/build-openwrt-apk.sh` assembles the `.apk` using `apk mkpkg` + (apk-tools 3.x ADB format) for OpenWrt 24.10+ snapshots and newer. ## File layout (installed) diff --git a/scripts/build-openwrt-apk.sh b/scripts/build-openwrt-apk.sh index 0940d0bd..78dfddc6 100755 --- a/scripts/build-openwrt-apk.sh +++ b/scripts/build-openwrt-apk.sh @@ -17,76 +17,73 @@ if [ ! -x "$SEA_DIR/rustdesk-console" ]; then exit 1 fi +if ! command -v apk >/dev/null 2>&1; then + echo "ERROR: 'apk' command not found. This script must run inside Alpine (apk-tools 3.x)." >&2 + exit 1 +fi + +APK_VERSION="${VERSION//-nightly./_p}" +APK_VERSION="${APK_VERSION}-r1" + WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT -DATA="$WORK/data" -mkdir -p "$DATA/usr/lib/rustdesk-console" "$DATA/usr/bin" \ - "$DATA/etc/init.d" "$DATA/etc/rustdesk-console" +PKGROOT="$WORK/pkgroot" +mkdir -p "$PKGROOT/usr/lib/rustdesk-console" "$PKGROOT/usr/bin" \ + "$PKGROOT/etc/init.d" "$PKGROOT/etc/rustdesk-console" -cp "$SEA_DIR/rustdesk-console" "$DATA/usr/lib/rustdesk-console/rustdesk-console" -chmod 0755 "$DATA/usr/lib/rustdesk-console/rustdesk-console" +cp "$SEA_DIR/rustdesk-console" "$PKGROOT/usr/lib/rustdesk-console/rustdesk-console" +chmod 0755 "$PKGROOT/usr/lib/rustdesk-console/rustdesk-console" if [ -d "$SEA_DIR/templates" ]; then - cp -a "$SEA_DIR/templates" "$DATA/usr/lib/rustdesk-console/templates" + cp -a "$SEA_DIR/templates" "$PKGROOT/usr/lib/rustdesk-console/templates" fi if [ -d "$SEA_DIR/node_modules" ]; then - cp -a "$SEA_DIR/node_modules" "$DATA/usr/lib/rustdesk-console/node_modules" + cp -a "$SEA_DIR/node_modules" "$PKGROOT/usr/lib/rustdesk-console/node_modules" fi -cat > "$DATA/usr/bin/rustdesk-console" <<'WRAPPER' +cat > "$PKGROOT/usr/bin/rustdesk-console" <<'WRAPPER' #!/bin/sh exec /usr/lib/rustdesk-console/rustdesk-console "$@" WRAPPER -chmod 0755 "$DATA/usr/bin/rustdesk-console" - -cp "$FILES_DIR/rustdesk-console.init" "$DATA/etc/init.d/rustdesk-console" -chmod 0755 "$DATA/etc/init.d/rustdesk-console" - -cp "$FILES_DIR/rustdesk-console.env" "$DATA/etc/rustdesk-console/rustdesk-console.env" -chmod 0644 "$DATA/etc/rustdesk-console/rustdesk-console.env" - -DATA_SIZE="$(du -sb "$DATA" | cut -f1)" -BUILD_DATE="$(date +%s)" - -CTRL="$WORK/control" -mkdir -p "$CTRL" - -cat > "$CTRL/.PKGINFO" < -size = $DATA_SIZE -arch = $ARCH -origin = rustdesk-console -maintainer = databk -depend = libc -depend = libstdcpp -depend = libgcc -EOF +chmod 0755 "$PKGROOT/usr/bin/rustdesk-console" -cat > "$CTRL/.post-install" <<'EOF' +cp "$FILES_DIR/rustdesk-console.init" "$PKGROOT/etc/init.d/rustdesk-console" +chmod 0755 "$PKGROOT/etc/init.d/rustdesk-console" + +cp "$FILES_DIR/rustdesk-console.env" "$PKGROOT/etc/rustdesk-console/rustdesk-console.env" +chmod 0644 "$PKGROOT/etc/rustdesk-console/rustdesk-console.env" + +cat > "$WORK/post-install.sh" <<'EOF' #!/bin/sh mkdir -p /var/lib/rustdesk-console exit 0 EOF -chmod 0755 "$CTRL/.post-install" +chmod 0755 "$WORK/post-install.sh" -cat > "$CTRL/.pre-deinstall" <<'EOF' +cat > "$WORK/pre-deinstall.sh" <<'EOF' #!/bin/sh /etc/init.d/rustdesk-console stop 2>/dev/null || true exit 0 EOF -chmod 0755 "$CTRL/.pre-deinstall" +chmod 0755 "$WORK/pre-deinstall.sh" OUT="$OUT_DIR/rustdesk-console_${VERSION}_${ARCH}.apk" -( cd "$CTRL" && tar czf "$WORK/control.tar.gz" --owner=0 --group=0 --mtime=@0 . ) -( cd "$DATA" && tar czf "$WORK/data.tar.gz" --owner=0 --group=0 --mtime=@0 . ) - -cat "$WORK/control.tar.gz" "$WORK/data.tar.gz" > "$OUT" - -echo "Built $OUT ($(du -h "$OUT" | cut -f1))" \ No newline at end of file +apk mkpkg \ + -I name:rustdesk-console \ + -I "version:$APK_VERSION" \ + -I "description:Enterprise-grade management platform for the RustDesk ecosystem." \ + -I "arch:$ARCH" \ + -I origin:rustdesk-console \ + -I "maintainer:databk " \ + -I url:https://github.com/databk/rustdesk-console \ + -I "depends:libc" \ + -I "depends:libstdcpp" \ + -I "depends:libgcc" \ + -s "post-install:$WORK/post-install.sh" \ + -s "pre-deinstall:$WORK/pre-deinstall.sh" \ + -F "$PKGROOT" \ + -o "$OUT" + +echo "Built $OUT ($(du -h "$OUT" | cut -f1))" From 102c7e25b6d93d1e91183644645e802af1ca376b Mon Sep 17 00:00:00 2001 From: hashbk Date: Sat, 5 Sep 2026 09:49:06 +0800 Subject: [PATCH 4/8] fix(openwrt): use noarch for apk to avoid arch mismatch on install OpenWrt apk uses specific arch names (e.g. aarch64_generic, aarch64_cortex-a72) and rejects packages with mismatched arch. Set arch to noarch so the .apk installs on any OpenWrt apk target; users select the correct CPU variant via the filename. --- .codegraph/.gitignore | 5 +++++ openwrt/README.md | 5 +++++ scripts/build-openwrt-apk.sh | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .codegraph/.gitignore diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 00000000..d20c0fe4 --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,5 @@ +# CodeGraph data files — local to each machine, not for committing. +# Ignore everything in .codegraph/ except this file itself, so transient +# files (the database, daemon.pid, sockets, logs) never show up in git. +* +!.gitignore diff --git a/openwrt/README.md b/openwrt/README.md index fa73a352..81ee794a 100644 --- a/openwrt/README.md +++ b/openwrt/README.md @@ -36,6 +36,11 @@ apk add --allow-untrusted rustdesk-console__x86_64.apk > a build key. For production feeds, sign the package with your own key and > configure `apk` trust accordingly. +> **Note:** The `.apk` uses `arch: noarch` so it installs on any OpenWrt apk +> target (e.g. `aarch64_generic`, `aarch64_cortex-a72`, `x86_64`). Make sure +> to download the file matching your CPU architecture (`x86_64` or `aarch64`) +> — the package itself does not enforce the CPU architecture. + Then enable and start the service: ```sh diff --git a/scripts/build-openwrt-apk.sh b/scripts/build-openwrt-apk.sh index 78dfddc6..47bf3443 100755 --- a/scripts/build-openwrt-apk.sh +++ b/scripts/build-openwrt-apk.sh @@ -73,8 +73,8 @@ OUT="$OUT_DIR/rustdesk-console_${VERSION}_${ARCH}.apk" apk mkpkg \ -I name:rustdesk-console \ -I "version:$APK_VERSION" \ - -I "description:Enterprise-grade management platform for the RustDesk ecosystem." \ - -I "arch:$ARCH" \ + -I "description:Enterprise-grade management platform for the RustDesk ecosystem. (musl $ARCH binary)" \ + -I arch:noarch \ -I origin:rustdesk-console \ -I "maintainer:databk " \ -I url:https://github.com/databk/rustdesk-console \ From a30da83cc1015fc328211db5017d0a43d12202ed Mon Sep 17 00:00:00 2001 From: hashbk Date: Sat, 5 Sep 2026 10:12:42 +0800 Subject: [PATCH 5/8] fix(openwrt): use gzip tar format for ipk instead of ar archive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenWrt opkg expects .ipk as a gzip-compressed tar archive containing debian-binary, control.tar.gz and data.tar.gz — not an ar archive (Debian .deb format). The previous ar-based output was rejected by opkg as 'Malformed package file'. Verified by installing into an OpenWrt 24.10.0 x86_64 rootfs with opkg. --- scripts/build-openwrt-ipk.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-openwrt-ipk.sh b/scripts/build-openwrt-ipk.sh index e2f242df..de54610e 100644 --- a/scripts/build-openwrt-ipk.sh +++ b/scripts/build-openwrt-ipk.sh @@ -86,6 +86,6 @@ printf '2.0\n' > "$WORK/debian-binary" ( cd "$CTRL" && tar czf "$WORK/control.tar.gz" --owner=0 --group=0 --mtime=@0 . ) OUT="$OUT_DIR/rustdesk-console_${VERSION}_${ARCH}.ipk" -ar rcD "$OUT" "$WORK/debian-binary" "$WORK/control.tar.gz" "$WORK/data.tar.gz" +( cd "$WORK" && tar czf "$OUT" --owner=0 --group=0 --mtime=@0 debian-binary control.tar.gz data.tar.gz ) echo "Built $OUT ($(du -h "$OUT" | cut -f1))" \ No newline at end of file From 0a1a99df78f1008b67f0346a365a8006de638739 Mon Sep 17 00:00:00 2001 From: hashbk Date: Sat, 5 Sep 2026 10:20:09 +0800 Subject: [PATCH 6/8] fix(git): rm ignore --- .codegraph/.gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore index d20c0fe4..c65969c4 100644 --- a/.codegraph/.gitignore +++ b/.codegraph/.gitignore @@ -2,4 +2,3 @@ # Ignore everything in .codegraph/ except this file itself, so transient # files (the database, daemon.pid, sockets, logs) never show up in git. * -!.gitignore From 698104574de77ecfa93f5e08239186c9fb1e33d6 Mon Sep 17 00:00:00 2001 From: hashbk Date: Sat, 5 Sep 2026 10:26:23 +0800 Subject: [PATCH 7/8] fix(git): rm ignore --- .codegraph/.gitignore | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .codegraph/.gitignore diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore deleted file mode 100644 index c65969c4..00000000 --- a/.codegraph/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# CodeGraph data files — local to each machine, not for committing. -# Ignore everything in .codegraph/ except this file itself, so transient -# files (the database, daemon.pid, sockets, logs) never show up in git. -* From 4cb43959547ff7c634d4ed8afa021186c1d67f8b Mon Sep 17 00:00:00 2001 From: hashbk Date: Sat, 5 Sep 2026 13:55:15 +0800 Subject: [PATCH 8/8] fix(openwrt): correct dependency package names for OpenWrt OpenWrt provides libstdc++ as 'libstdcpp6' and libgcc_s as 'libgcc1', not 'libstdcpp'/'libgcc'. The wrong names caused apk install to fail with '2 errors' because the dependencies could not be resolved. Verified installation succeeds on OpenWrt x86_64 rootfs with apk. --- openwrt/Makefile | 2 +- scripts/build-openwrt-apk.sh | 4 ++-- scripts/build-openwrt-ipk.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openwrt/Makefile b/openwrt/Makefile index a78ffe2b..55e33ee2 100644 --- a/openwrt/Makefile +++ b/openwrt/Makefile @@ -25,7 +25,7 @@ define Package/rustdesk-console CATEGORY:=Network SUBMENU:=RustDesk TITLE:=RustDesk Console management platform - DEPENDS:=+libc +libstdcpp +libgcc + DEPENDS:=+libc +libstdcpp6 +libgcc1 MAINTAINER:=databk endef diff --git a/scripts/build-openwrt-apk.sh b/scripts/build-openwrt-apk.sh index 47bf3443..53aa429f 100755 --- a/scripts/build-openwrt-apk.sh +++ b/scripts/build-openwrt-apk.sh @@ -79,8 +79,8 @@ apk mkpkg \ -I "maintainer:databk " \ -I url:https://github.com/databk/rustdesk-console \ -I "depends:libc" \ - -I "depends:libstdcpp" \ - -I "depends:libgcc" \ + -I "depends:libstdcpp6" \ + -I "depends:libgcc1" \ -s "post-install:$WORK/post-install.sh" \ -s "pre-deinstall:$WORK/pre-deinstall.sh" \ -F "$PKGROOT" \ diff --git a/scripts/build-openwrt-ipk.sh b/scripts/build-openwrt-ipk.sh index de54610e..477ec955 100644 --- a/scripts/build-openwrt-ipk.sh +++ b/scripts/build-openwrt-ipk.sh @@ -56,7 +56,7 @@ Architecture: $ARCH Maintainer: databk Section: net Priority: optional -Depends: libc, libstdcpp, libgcc +Depends: libc, libstdcpp6, libgcc1 Description: Enterprise-grade management platform for the RustDesk ecosystem. Self-hosted console as an alternative to RustDesk Server Pro. Ships prebuilt musl binaries (Node.js SEA + sqlite3 + sharp).