diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a5a92458..109e0789 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -204,3 +204,68 @@ jobs: rustdesk-console-${{ matrix.target }}.tar.gz rustdesk-console-${{ matrix.target }}.zip fail_on_unmatched_files: false + + musl-build: + name: Build musl SEA + ipk + apk (${{ 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: Build apk + run: | + VERSION=${{ needs.nightly.outputs.version }} + 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: | + 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 + 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 da94ffd6..9b0ba817 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -149,3 +149,59 @@ jobs: rustdesk-console-${{ matrix.target }}.tar.gz rustdesk-console-${{ matrix.target }}.zip fail_on_unmatched_files: false + + musl-build: + name: Build musl SEA + ipk + apk (${{ 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: Build apk + run: | + VERSION=${{ needs.release.outputs.version }} + 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: | + 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 + rustdesk-console_${{ needs.release.outputs.version }}_${{ matrix.arch }}.apk + fail_on_unmatched_files: false diff --git a/README.md b/README.md index 94c62a34..358f5640 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,37 @@ npm run start:prod +
+๐Ÿ“ฆ OpenWrt / ImmortalWrt (soft router) + +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 +``` + +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..55e33ee2 --- /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 +libstdcpp6 +libgcc1 + 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..81ee794a --- /dev/null +++ b/openwrt/README.md @@ -0,0 +1,121 @@ +# 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 `.ipk` and `.apk` +packages 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 package + +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. + +> **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 +/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, `.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 + 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. +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) + +``` +/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-apk.sh b/scripts/build-openwrt-apk.sh new file mode 100755 index 00000000..53aa429f --- /dev/null +++ b/scripts/build-openwrt-apk.sh @@ -0,0 +1,89 @@ +#!/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 + +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 + +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" "$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" "$PKGROOT/usr/lib/rustdesk-console/templates" +fi +if [ -d "$SEA_DIR/node_modules" ]; then + cp -a "$SEA_DIR/node_modules" "$PKGROOT/usr/lib/rustdesk-console/node_modules" +fi + +cat > "$PKGROOT/usr/bin/rustdesk-console" <<'WRAPPER' +#!/bin/sh +exec /usr/lib/rustdesk-console/rustdesk-console "$@" +WRAPPER +chmod 0755 "$PKGROOT/usr/bin/rustdesk-console" + +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 "$WORK/post-install.sh" + +cat > "$WORK/pre-deinstall.sh" <<'EOF' +#!/bin/sh +/etc/init.d/rustdesk-console stop 2>/dev/null || true +exit 0 +EOF +chmod 0755 "$WORK/pre-deinstall.sh" + +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. (musl $ARCH binary)" \ + -I arch:noarch \ + -I origin:rustdesk-console \ + -I "maintainer:databk " \ + -I url:https://github.com/databk/rustdesk-console \ + -I "depends:libc" \ + -I "depends:libstdcpp6" \ + -I "depends:libgcc1" \ + -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))" diff --git a/scripts/build-openwrt-ipk.sh b/scripts/build-openwrt-ipk.sh new file mode 100644 index 00000000..477ec955 --- /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, 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). +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" +( 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