Files

342 lines
15 KiB
Bash

#!/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-<ver>-x86_64.AppImage) -- single-file, run anywhere,
# libav* bundled via linuxdeploy; no install needed.
# * .deb (netdesk_<ver>_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 <triple>
#
# (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 <https://akronlogic.com>"
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" <<CONTROL
Package: $PKG_NAME
Version: $VERSION
Section: net
Priority: optional
Architecture: $DEB_ARCH
Maintainer: $MAINTAINER
Installed-Size: $INSTALLED_KB
Depends: $DEPENDS
Recommends: intel-media-va-driver | i965-va-driver | mesa-va-drivers
Suggests: vainfo
Homepage: $HOMEPAGE
Description: NetDesk remote desktop viewer by Akronlogic
NetDesk is a secure, self-hosted remote desktop client. The native viewer
renders the remote screen with GPU-filtered scaling over a TeamViewer/
RustDesk-style toolbar (eframe + egui).
.
H.264 video is hardware-decoded with VAAPI through FFmpeg/libavcodec when a
GPU decoder is available, and falls back to an openh264 software decoder
otherwise, so it also works on headless / GPU-less machines.
CONTROL
# maintainer scripts: refresh the desktop + icon caches ------------------
cat > "$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