#!/usr/bin/env bash # # NetDesk by Akronlogic -- LINUX build + packaging script. # # Builds the release viewer (cargo build --release -p netdesk-viewer) for the # x86_64-unknown-linux-gnu target, then bundles TWO Linux distributions: # # * AppImage (NetDesk--x86_64.AppImage) -- single-file, run anywhere, # libav* bundled via linuxdeploy; no install needed. # * .deb (netdesk__amd64.deb) -- dpkg package with a # .desktop launcher + hicolor icons; apt/dpkg install. # # The Linux decode path (core/src/decode_vaapi.rs) hardware-decodes H.264 with # VAAPI through FFmpeg/libavcodec, so BUILDING needs the system FFmpeg + VAAPI # development libraries present (see docs/build-linux.md). At RUNTIME VAAPI is # optional -- the core falls back to the openh264 software decoder when no GPU # decoder is available. # # Outputs land in packaging/dist/linux/ . # # Usage: # export PATH="$HOME/.cargo/bin:$PATH" # packaging/build-linux.sh # build + AppImage + .deb # packaging/build-linux.sh --skip-build # reuse an existing binary # packaging/build-linux.sh --no-appimage # .deb only # packaging/build-linux.sh --no-deb # AppImage only # packaging/build-linux.sh --target # # (c) Akronlogic. All rights reserved. set -euo pipefail # --------------------------------------------------------------------------- # product / packaging identity # --------------------------------------------------------------------------- APP_NAME="NetDesk" BIN_NAME="netdesk-viewer" # cargo [[bin]] name APP_ID="com.akronlogic.netdesk" # == viewer ViewportBuilder::with_app_id PKG_NAME="netdesk" # dpkg package name TARGET="x86_64-unknown-linux-gnu" DEB_ARCH="amd64" MAINTAINER="Akronlogic " HOMEPAGE="https://akronlogic.com" SKIP_BUILD=0 DO_APPIMAGE=1 DO_DEB=1 # --------------------------------------------------------------------------- # tiny coloured logging helpers # --------------------------------------------------------------------------- say() { printf '\033[36m%s\033[0m\n' "$*"; } # cyan (headline) step() { printf '\033[33m%s\033[0m\n' "$*"; } # yellow(step) ok() { printf '\033[32m%s\033[0m\n' "$*"; } # green (success) warn() { printf '\033[33m%s\033[0m\n' "WARN: $*" >&2; } die() { printf '\033[31m%s\033[0m\n' "ERROR: $*" >&2; exit 1; } usage() { sed -n '2,40p' "$0" | sed 's/^#\s\{0,1\}//' } # --------------------------------------------------------------------------- # args # --------------------------------------------------------------------------- while [ $# -gt 0 ]; do case "$1" in --skip-build) SKIP_BUILD=1 ;; --no-appimage) DO_APPIMAGE=0 ;; --no-deb) DO_DEB=0 ;; --target) shift; [ $# -gt 0 ] || die "--target needs a value"; TARGET="$1" ;; --target=*) TARGET="${1#*=}" ;; -h|--help) usage; exit 0 ;; *) die "unknown argument: $1 (try --help)" ;; esac shift done # --------------------------------------------------------------------------- # paths # --------------------------------------------------------------------------- PKG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(dirname "$PKG_DIR")" ASSETS_DIR="$ROOT_DIR/assets" DIST_DIR="$PKG_DIR/dist/linux" TOOLS_DIR="$PKG_DIR/dist/.tools" VERSION="$(grep -m1 '^version' "$ROOT_DIR/Cargo.toml" | sed -E 's/.*"([^"]+)".*/\1/')" [ -n "$VERSION" ] || VERSION="0.0.0" BIN="$ROOT_DIR/target/$TARGET/release/$BIN_NAME" DESKTOP_SRC="$DIST_DIR/$APP_ID.desktop" say "=== NetDesk Linux build (Akronlogic) ===" echo " root : $ROOT_DIR" echo " version : $VERSION" echo " target : $TARGET" echo " dist : $DIST_DIR" echo " outputs : $([ $DO_APPIMAGE -eq 1 ] && echo -n 'AppImage ')$([ $DO_DEB -eq 1 ] && echo -n '.deb')" # --------------------------------------------------------------------------- # preflight # --------------------------------------------------------------------------- [ "$(uname -s)" = "Linux" ] || die "this script must run on Linux (building the x86_64-unknown-linux-gnu viewer)." # make sure a rustup/cargo install under ~/.cargo is reachable case ":$PATH:" in *":$HOME/.cargo/bin:"*) : ;; *) PATH="$HOME/.cargo/bin:$PATH" ;; esac export PATH if [ "$SKIP_BUILD" -eq 0 ]; then command -v cargo >/dev/null 2>&1 || die "cargo not found on PATH. Install Rust (https://rustup.rs) and/or 'export PATH=\$HOME/.cargo/bin:\$PATH'." # The decode_vaapi.rs backend links FFmpeg via the ffmpeg-next crate; its # build script (ffmpeg-sys-next) locates libav* with pkg-config and needs # libclang (bindgen). Fail fast with a copy-pasteable install hint. if ! command -v pkg-config >/dev/null 2>&1 || ! pkg-config --exists libavcodec 2>/dev/null; then warn "FFmpeg development libraries (libavcodec) were not found via pkg-config." echo " Debian/Ubuntu:" echo " sudo apt install -y build-essential clang pkg-config nasm \\" echo " libavcodec-dev libavformat-dev libavutil-dev libswscale-dev \\" echo " libavfilter-dev libavdevice-dev libswresample-dev \\" echo " libva-dev libgl1-mesa-dev" echo " Fedora:" echo " sudo dnf install -y gcc gcc-c++ clang pkgconf-pkg-config nasm \\" echo " ffmpeg-devel libva-devel mesa-libGL-devel" die "install the build dependencies above, then re-run (see docs/build-linux.md)." fi command -v clang >/dev/null 2>&1 || warn "clang/libclang not found -- ffmpeg-sys-next bindgen will fail. Install 'clang'." fi mkdir -p "$DIST_DIR" "$TOOLS_DIR" # --------------------------------------------------------------------------- # 1. build the release viewer # --------------------------------------------------------------------------- if [ "$SKIP_BUILD" -eq 0 ]; then step "[1] cargo build --release -p $BIN_NAME --target $TARGET" # add the target when rustup is in use; a no-op / harmless on a native host. command -v rustup >/dev/null 2>&1 && rustup target add "$TARGET" >/dev/null 2>&1 || true ( cd "$ROOT_DIR" && cargo build --release -p "$BIN_NAME" --target "$TARGET" ) else step "[1] --skip-build: reusing existing binary" fi [ -x "$BIN" ] || die "viewer binary not found: $BIN (remove --skip-build to build it)." echo " binary : $BIN ($(du -h "$BIN" | cut -f1))" # --------------------------------------------------------------------------- # 2. shared .desktop launcher (used by BOTH the AppImage and the .deb) # --------------------------------------------------------------------------- # The .desktop filename and the Icon= key both use the app-id so GNOME/KDE # associate the running window (winit sets WM_CLASS / Wayland app_id from # with_app_id("com.akronlogic.netdesk")) with this launcher + its icon. step "[2] writing $APP_ID.desktop" cat > "$DESKTOP_SRC" <<'DESKTOP' [Desktop Entry] Type=Application Version=1.0 Name=NetDesk GenericName=Remote Desktop Comment=Secure remote desktop viewer by Akronlogic Exec=netdesk-viewer TryExec=netdesk-viewer Icon=com.akronlogic.netdesk Terminal=false Categories=Network;RemoteAccess; Keywords=remote;desktop;screen;share;vnc;rdp;netdesk; StartupNotify=true StartupWMClass=com.akronlogic.netdesk DESKTOP ARTIFACTS=() # =========================================================================== # 3. .deb (built first -- needs no network and no FUSE) # =========================================================================== if [ "$DO_DEB" -eq 1 ]; then step "[3] building .deb" command -v dpkg-deb >/dev/null 2>&1 || die "dpkg-deb not found (install the 'dpkg' package) -- cannot build the .deb. Use --no-deb to skip." DEB_ROOT="$DIST_DIR/deb" rm -rf "$DEB_ROOT" # payload ---------------------------------------------------------------- install -Dm755 "$BIN" "$DEB_ROOT/usr/bin/$BIN_NAME" ln -sf "$BIN_NAME" "$DEB_ROOT/usr/bin/netdesk" # convenience alias install -Dm644 "$DESKTOP_SRC" "$DEB_ROOT/usr/share/applications/$APP_ID.desktop" install -Dm644 "$ASSETS_DIR/icon-512.png" "$DEB_ROOT/usr/share/icons/hicolor/512x512/apps/$APP_ID.png" install -Dm644 "$ASSETS_DIR/icon.png" "$DEB_ROOT/usr/share/icons/hicolor/256x256/apps/$APP_ID.png" install -Dm644 "$ASSETS_DIR/logo.svg" "$DEB_ROOT/usr/share/icons/hicolor/scalable/apps/$APP_ID.svg" # copyright -------------------------------------------------------------- install -d "$DEB_ROOT/usr/share/doc/$PKG_NAME" cat > "$DEB_ROOT/usr/share/doc/$PKG_NAME/copyright" <<'COPYRIGHT' NetDesk -- native remote desktop viewer. Developed by Akronlogic (https://akronlogic.com). Copyright (c) Akronlogic. All rights reserved. License: Proprietary. Full IP ownership, no AGPL. COPYRIGHT # Depends ----------------------------------------------------------------- # Curated runtime dependency set. NOTE: libGL, libva, libX11/xcb, Wayland and # libxkbcommon are dlopen()ed at runtime (by glutin/winit and by libavcodec's # VAAPI backend), so ldd/dpkg-shlibdeps would MISS them -- they are listed # explicitly here. FFmpeg sonames differ per release, so each libav* uses # alternatives ending in the generic 'ffmpeg' package as a catch-all. BASE_DEPS="libc6, libgcc-s1, libstdc++6" FFMPEG_DEPS="libavcodec60 | libavcodec59 | libavcodec58 | ffmpeg, libavutil58 | libavutil57 | libavutil56 | ffmpeg, libswscale7 | libswscale6 | libswscale5 | ffmpeg" DLOPEN_DEPS="libgl1, libva2, libva-drm2, libva-x11-2, libxkbcommon0, libx11-6, libxcb1" DEPENDS="$BASE_DEPS, $FFMPEG_DEPS, $DLOPEN_DEPS" INSTALLED_KB="$(du -sk "$DEB_ROOT" | cut -f1)" install -d "$DEB_ROOT/DEBIAN" cat > "$DEB_ROOT/DEBIAN/control" < "$DEB_ROOT/DEBIAN/postinst" <<'POSTINST' #!/bin/sh set -e if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications 2>/dev/null || true fi if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor 2>/dev/null || true fi exit 0 POSTINST cat > "$DEB_ROOT/DEBIAN/postrm" <<'POSTRM' #!/bin/sh set -e if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications 2>/dev/null || true fi if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor 2>/dev/null || true fi exit 0 POSTRM chmod 0755 "$DEB_ROOT/DEBIAN/postinst" "$DEB_ROOT/DEBIAN/postrm" OUT_DEB="$DIST_DIR/${PKG_NAME}_${VERSION}_${DEB_ARCH}.deb" rm -f "$OUT_DEB" # --root-owner-group => root:root ownership with no fakeroot (dpkg >= 1.19). if ! dpkg-deb --build --root-owner-group "$DEB_ROOT" "$OUT_DEB" 2>/dev/null; then warn "dpkg-deb --root-owner-group unavailable; building without it (files owned by the current uid)." dpkg-deb --build "$DEB_ROOT" "$OUT_DEB" fi [ -f "$OUT_DEB" ] || die "dpkg-deb reported success but $OUT_DEB is missing." ARTIFACTS+=("$OUT_DEB") ok " .deb : $OUT_DEB ($(du -h "$OUT_DEB" | cut -f1))" fi # =========================================================================== # 4. AppImage (via linuxdeploy; bundles libav* etc.) # =========================================================================== if [ "$DO_APPIMAGE" -eq 1 ]; then step "[4] building AppImage" # 4a. obtain linuxdeploy (cache under dist/.tools, or set LINUXDEPLOY=/path) LINUXDEPLOY="${LINUXDEPLOY:-$TOOLS_DIR/linuxdeploy-x86_64.AppImage}" if [ ! -x "$LINUXDEPLOY" ]; then url="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" echo " fetching linuxdeploy -> $LINUXDEPLOY" if command -v curl >/dev/null 2>&1; then curl -fL --retry 3 -o "$LINUXDEPLOY" "$url" || die "failed to download linuxdeploy (set LINUXDEPLOY=/path to a local copy, or --no-appimage)." elif command -v wget >/dev/null 2>&1; then wget -q -O "$LINUXDEPLOY" "$url" || die "failed to download linuxdeploy (set LINUXDEPLOY=/path to a local copy, or --no-appimage)." else die "need curl or wget to download linuxdeploy. Install one, set LINUXDEPLOY=/path, or pass --no-appimage." fi chmod +x "$LINUXDEPLOY" fi # run AppImage tools without FUSE (CI / containers / minimal hosts). export APPIMAGE_EXTRACT_AND_RUN=1 # 4b. assemble the AppDir APPDIR="$DIST_DIR/AppDir" rm -rf "$APPDIR" install -Dm755 "$BIN" "$APPDIR/usr/bin/$BIN_NAME" install -Dm644 "$DESKTOP_SRC" "$APPDIR/usr/share/applications/$APP_ID.desktop" install -Dm644 "$ASSETS_DIR/icon-512.png" "$APPDIR/usr/share/icons/hicolor/512x512/apps/$APP_ID.png" install -Dm644 "$ASSETS_DIR/icon.png" "$APPDIR/usr/share/icons/hicolor/256x256/apps/$APP_ID.png" install -Dm644 "$ASSETS_DIR/logo.svg" "$APPDIR/usr/share/icons/hicolor/scalable/apps/$APP_ID.svg" # top-level icon + .DirIcon + desktop that appimagetool expects at the root cp "$ASSETS_DIR/icon-512.png" "$APPDIR/$APP_ID.png" cp "$DESKTOP_SRC" "$APPDIR/$APP_ID.desktop" ( cd "$APPDIR" && ln -sf "$APP_ID.png" .DirIcon ) # 4c. run linuxdeploy: it ldd-scans usr/bin, copies the app's shared libs # (libav*, etc.) into the AppDir, writes AppRun, and emits the AppImage. # libGL / libX11 / Wayland / libva are intentionally NOT bundled (they are # dlopen()ed and must come from the host graphics stack). OUT_APPIMAGE="$DIST_DIR/${APP_NAME}-${VERSION}-x86_64.AppImage" rm -f "$OUT_APPIMAGE" if ( cd "$DIST_DIR" && OUTPUT="$(basename "$OUT_APPIMAGE")" VERSION="$VERSION" \ "$LINUXDEPLOY" \ --appdir "$APPDIR" \ -e "$APPDIR/usr/bin/$BIN_NAME" \ -d "$APPDIR/usr/share/applications/$APP_ID.desktop" \ -i "$APPDIR/usr/share/icons/hicolor/512x512/apps/$APP_ID.png" \ --icon-filename "$APP_ID" \ --output appimage ) && [ -f "$OUT_APPIMAGE" ]; then chmod +x "$OUT_APPIMAGE" ARTIFACTS+=("$OUT_APPIMAGE") ok " AppImage: $OUT_APPIMAGE ($(du -h "$OUT_APPIMAGE" | cut -f1))" else warn "linuxdeploy did not produce an AppImage (network / FUSE / tool error). The .deb above is unaffected." fi fi # --------------------------------------------------------------------------- # summary # --------------------------------------------------------------------------- echo if [ "${#ARTIFACTS[@]}" -gt 0 ]; then ok "Linux build complete. Artifacts:" for a in "${ARTIFACTS[@]}"; do echo " $a ($(du -h "$a" | cut -f1))"; done else die "no artifacts were produced." fi