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