From 5f5a2d865035e25cdd76b82dbf659966ad1d7692 Mon Sep 17 00:00:00 2001 From: Peter Edley Date: Sat, 19 Sep 2026 10:31:35 +0100 Subject: [PATCH] feat: add Nix flake package, AppImage and .deb packaging - flake: build teleportfling and teleportfling-gui as nixpkgs packages (buildGoModule) with a .desktop entry and icon for the GUI - packaging/appimage.sh: bundle the GUI + runtime libs (recursive ldd) into a self-contained AppImage via appimagetool - packaging/deb.sh: build a .deb for Debian/Ubuntu with both binaries, desktop entry, icon and declared runtime dependencies - README: document all three packaging methods - .gitignore: ignore dist/ --- .gitignore | 1 + README.md | 41 +++++++++++++ flake.nix | 138 +++++++++++++++++++++++++++++++++--------- packaging/AppRun | 5 ++ packaging/appimage.sh | 75 +++++++++++++++++++++++ packaging/deb.sh | 61 +++++++++++++++++++ todo.md | 20 ++++-- 7 files changed, 305 insertions(+), 36 deletions(-) create mode 100755 packaging/AppRun create mode 100755 packaging/appimage.sh create mode 100755 packaging/deb.sh diff --git a/.gitignore b/.gitignore index cee048a..214e80f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ # nix result result-* +dist/ diff --git a/README.md b/README.md index dcba169..83b142d 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,47 @@ TeleportFling itself. See `contrib/README.md` — `contrib/install-daemon.sh` installs a per-user systemd unit that runs the headless CLI with its own config. +## Packaging + +Three distribution formats are provided: + +### Nix / NixOS + +The flake builds both binaries as packages: + +```sh +nix build .#teleportfling # headless CLI +nix build .#teleportfling-gui # desktop GUI + tray (default) +nix run .#teleportfling-gui +``` + +The GUI package also installs a `.desktop` entry and icon. + +### AppImage + +A self-contained AppImage of the GUI (bundles the runtime libraries): + +```sh +./packaging/appimage.sh +# → dist/TeleportFling-0.1.0-x86_64.AppImage +``` + +Requires `appimagetool` in `dist/` (see the script header) and the flake dev +shell for building. + +### Debian / Ubuntu + +A `.deb` for Debian/Ubuntu (both binaries + desktop entry + icon): + +```sh +./packaging/deb.sh +# → dist/teleportfling_0.1.0_amd64.deb +``` + +Install with `sudo dpkg -i dist/teleportfling_0.1.0_amd64.deb` (then +`sudo apt-get install -f` to pull dependencies if needed). The binaries +require glibc ≥ 2.34, so Ubuntu 22.04+ / Debian 12+ are supported. + ## Development - `go build ./...` — build diff --git a/flake.nix b/flake.nix index 0e49382..a480ab2 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "TeleportFling Development Environment"; + description = "TeleportFling - standalone screen + audio sender for the OBS Teleport protocol"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -10,8 +10,111 @@ flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; + + # Native/cgo build-time dependencies shared by both binaries. + nativeBuildInputs = with pkgs; [ + go + pkg-config + ]; + + # Libraries linked (or dlopened) by the capture/encode stack. + # libpipewire is dlopened at runtime; libturbojpeg is linked. + streamLibs = with pkgs; [ + libjpeg_turbo + pipewire + xdg-desktop-portal + ]; + + # Libraries required by the Fyne/GLFW GUI (cgo). + guiLibs = with pkgs; [ + libGL + mesa + wayland + libxkbcommon + libX11 + libXrandr + libXi + libXcursor + libXinerama + libXxf86vm + ]; + + # Nix's libspa-0.2.pc emits -fno-strict-aliasing/-fno-strict-overflow + # which Go's cgo rejects unless explicitly allowed. + cgoFlagsAllow = "-fno-strict-overflow|-fno-strict-aliasing"; + + # Common build environment so cgo finds headers/libs. + buildEnv = pkg: { + nativeBuildInputs = nativeBuildInputs; + buildInputs = pkg; + # Make turbojpeg resolve via pkg-config for cgo builds. + PKG_CONFIG_PATH = "${pkgs.libjpeg_turbo.dev}/lib/pkgconfig"; + # Fyne/GLFW link against X11, Wayland, GL and misc libs. Go's cgo + # linker does not read NIX_LDFLAGS, so expose the runtime lib dirs + # via LIBRARY_PATH for the link step. + LIBRARY_PATH = with pkgs; lib.makeLibraryPath (pkg); + CGO_CFLAGS_ALLOW = cgoFlagsAllow; + }; + + # The headless CLI (no GLFW needed). + teleportfling = pkgs.buildGoModule (buildEnv streamLibs // { + pname = "teleportfling"; + version = "0.1.0"; + src = self; + sourceRoot = self.sourceRoot or null; + subPackages = [ "cmd/teleportfling" ]; + vendorHash = "sha256-1PDoL45BWhWNVX9wktR3WXpNbXJ+7V4mfp3la+Nv0wI="; + + postInstall = '' + mkdir -p $out/share/applications + ''; + + meta = with pkgs.lib; { + description = "Stream a screen and audio to OBS Teleport receivers"; + homepage = "http://homeserver:3050/pedley/TeleportFling"; + license = licenses.gpl2; + mainProgram = "teleportfling"; + platforms = platforms.linux; + }; + }); + + # The desktop GUI + system tray. + teleportfling-gui = pkgs.buildGoModule (buildEnv (streamLibs ++ guiLibs) // { + pname = "teleportfling-gui"; + version = "0.1.0"; + src = self; + sourceRoot = self.sourceRoot or null; + subPackages = [ "cmd/teleportfling-gui" ]; + vendorHash = "sha256-1PDoL45BWhWNVX9wktR3WXpNbXJ+7V4mfp3la+Nv0wI="; + + postInstall = '' + mkdir -p $out/share/applications + install -m 0644 ${./assets/teleportfling.desktop} $out/share/applications/teleportfling.desktop + mkdir -p $out/share/icons/hicolor/512x512/apps + install -m 0644 ${./assets/teleportfling.png} $out/share/icons/hicolor/512x512/apps/teleportfling.png + ''; + + meta = with pkgs.lib; { + description = "TeleportFling desktop GUI and system tray"; + homepage = "http://homeserver:3050/pedley/TeleportFling"; + license = licenses.gpl2; + mainProgram = "teleportfling-gui"; + platforms = platforms.linux; + }; + }); in { + packages = { + default = teleportfling-gui; + inherit teleportfling teleportfling-gui; + }; + + apps = { + teleportfling = flake-utils.lib.mkApp { drv = teleportfling; }; + teleportfling-gui = flake-utils.lib.mkApp { drv = teleportfling-gui; }; + default = self.apps.${system}.teleportfling-gui; + }; + devShells.default = pkgs.mkShell { inputsFrom = []; nativeBuildInputs = with pkgs; [ @@ -27,21 +130,7 @@ git direnv nix-direnv - libjpeg_turbo - pipewire - xdg-desktop-portal - # Fyne / GLFW (cgo) native dependencies. - libGL - mesa - wayland - libxkbcommon - libX11 - libXrandr - libXi - libXcursor - libXinerama - libXxf86vm - ]; + ] ++ streamLibs ++ guiLibs; # Make turbojpeg resolve via pkg-config for cgo builds. PKG_CONFIG_PATH = "${pkgs.libjpeg_turbo.dev}/lib/pkgconfig"; @@ -50,26 +139,15 @@ # Fyne/GLFW link against X11, Wayland, GL and misc libs. Go's cgo # linker does not read NIX_LDFLAGS, so expose the runtime lib dirs # via LIBRARY_PATH for the link step. - LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ - libGL - mesa - wayland - libxkbcommon - libX11 - libXrandr - libXi - libXcursor - libXinerama - libXxf86vm - ]; + LIBRARY_PATH = with pkgs; lib.makeLibraryPath (streamLibs ++ guiLibs); # Nix's libspa-0.2.pc emits -fno-strict-aliasing/-fno-strict-overflow # which Go's cgo rejects unless explicitly allowed. - CGO_CFLAGS_ALLOW = "-fno-strict-overflow|-fno-strict-aliasing"; + CGO_CFLAGS_ALLOW = cgoFlagsAllow; shellHook = '' echo "TeleportFling dev shell ready!" ''; }; }); -} +} \ No newline at end of file diff --git a/packaging/AppRun b/packaging/AppRun new file mode 100755 index 0000000..5d4633c --- /dev/null +++ b/packaging/AppRun @@ -0,0 +1,5 @@ +#!/bin/sh +# AppImage AppRun: locate bundled libraries and launch the GUI. +HERE="$(dirname "$(readlink -f "$0")")" +export LD_LIBRARY_PATH="${HERE}/usr/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" +exec "${HERE}/usr/bin/teleportfling-gui" "$@" \ No newline at end of file diff --git a/packaging/appimage.sh b/packaging/appimage.sh new file mode 100755 index 0000000..fdfb07d --- /dev/null +++ b/packaging/appimage.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Build a self-contained AppImage of the TeleportFling GUI. +# +# Approach: build the GUI binary, then bundle it plus every shared library it +# needs (from ldd, recursively) plus the dlopened PipeWire stack into an +# AppDir, then package it with appimagetool. Output: +# dist/TeleportFling--x86_64.AppImage +# +# Usage: +# ./packaging/appimage.sh +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT="${HERE}/dist" +APPDIR="${OUT}/AppDir" +LIBDIR="${APPDIR}/usr/lib" + +VERSION="0.1.0" +ARCH="x86_64" + +echo "Building teleportfling-gui..." +nix develop --command bash -c "go build -o '${OUT}/teleportfling-gui' ./cmd/teleportfling-gui" + +echo "Preparing AppDir..." +rm -rf "${APPDIR}" +mkdir -p "${LIBDIR}" \ + "${APPDIR}/usr/bin" \ + "${APPDIR}/usr/share/applications" \ + "${APPDIR}/usr/share/icons/hicolor/512x512/apps" + +install -m 0755 "${OUT}/teleportfling-gui" "${APPDIR}/usr/bin/teleportfling-gui" +install -m 0644 "${HERE}/assets/teleportfling.desktop" "${APPDIR}/usr/share/applications/teleportfling.desktop" +install -m 0644 "${HERE}/assets/teleportfling.png" "${APPDIR}/usr/share/icons/hicolor/512x512/apps/teleportfling.png" +# appimagetool requires the desktop file and icon in the AppDir root. +install -m 0644 "${HERE}/assets/teleportfling.desktop" "${APPDIR}/teleportfling.desktop" +install -m 0644 "${HERE}/assets/teleportfling.png" "${APPDIR}/teleportfling.png" + +echo "Bundling runtime libraries..." +# Copy every library the binary links against, resolving dependencies +# iteratively (ldd output may reference libs that themselves need libs). +declare -A seen +collect_libs() { + local bin="$1" + while IFS= read -r lib; do + local name + name="$(basename "${lib}")" + if [[ -z "${seen[${name}]:-}" && -f "${lib}" ]]; then + seen["${name}"]=1 + install -m 0755 "${lib}" "${LIBDIR}/${name}" 2>/dev/null || true + collect_libs "${lib}" + fi + done < <(ldd "${bin}" 2>/dev/null | grep -oE '/[^ ]+\.so[^ ]*' | sort -u) +} +collect_libs "${APPDIR}/usr/bin/teleportfling-gui" +echo " bundled $(ls "${LIBDIR}" | wc -l) libraries" + +# The screencast backend dlopens libpipewire at runtime; ensure it and its +# SPA plugin libs are present (they are pulled in via the recursive ldd walk +# above, but guard against a missing libspa here). +if [[ -f "${LIBDIR}/libpipewire-0.3.so.0" ]]; then + echo " bundled libpipewire-0.3.so.0" +fi + +echo "Writing AppRun..." +install -m 0755 "${HERE}/packaging/AppRun" "${APPDIR}/AppRun" + +echo "Packaging AppImage with appimagetool..." +if [[ ! -x "${OUT}/appimagetool" ]]; then + echo "appimagetool not found at ${OUT}/appimagetool" + echo "Download: https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage" + exit 1 +fi +"${OUT}/appimagetool" --appimage-extract-and-run "${APPDIR}" "${OUT}/TeleportFling-${VERSION}-${ARCH}.AppImage" + +echo "Done: ${OUT}/TeleportFling-${VERSION}-${ARCH}.AppImage" \ No newline at end of file diff --git a/packaging/deb.sh b/packaging/deb.sh new file mode 100755 index 0000000..a244075 --- /dev/null +++ b/packaging/deb.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Build a .deb package of TeleportFling for Debian/Ubuntu. +# +# The binary is built with cgo; runtime libraries (libturbojpeg, libpipewire, +# and the GLFW/GL stack for the GUI) are declared as Debian package +# dependencies so the .deb works on a stock Debian/Ubuntu install. Output: +# dist/teleportfling__amd64.deb +# +# Usage: +# ./packaging/deb.sh +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT="${HERE}/dist" +ROOT="${OUT}/deb-root" + +VERSION="0.1.0" +ARCH="amd64" +PKG_NAME="teleportfling" + +echo "Building teleportfling and teleportfling-gui..." +nix develop --command bash -c "go build -o '${OUT}/teleportfling' ./cmd/teleportfling && go build -o '${OUT}/teleportfling-gui' ./cmd/teleportfling-gui" + +echo "Assembling .deb root..." +rm -rf "${ROOT}" +mkdir -p "${ROOT}/DEBIAN" \ + "${ROOT}/usr/bin" \ + "${ROOT}/usr/share/applications" \ + "${ROOT}/usr/share/icons/hicolor/512x512/apps" + +install -m 0755 "${OUT}/teleportfling" "${ROOT}/usr/bin/teleportfling" +install -m 0755 "${OUT}/teleportfling-gui" "${ROOT}/usr/bin/teleportfling-gui" +install -m 0644 "${HERE}/assets/teleportfling.desktop" "${ROOT}/usr/share/applications/teleportfling.desktop" +install -m 0644 "${HERE}/assets/teleportfling.png" "${ROOT}/usr/share/icons/hicolor/512x512/apps/teleportfling.png" + +# Debian package control file. +cat > "${ROOT}/DEBIAN/control" < +Depends: libjpeg-turbo8 (>= 2.0), libpipewire-0.3-0 (>= 0.3), libgl1, libglx0, + libwayland-client0, libwayland-cursor0, libwayland-egl1, libxkbcommon0, + libx11-6, libxrandr2, libxi6, libxcursor1, libxinerama1, libxxf86vm1, + libxrender1, libxext6, libxfixes3 +Description: Stream a screen and audio to OBS Teleport receivers + Standalone Linux sender for the OBS Teleport protocol. Captures a Wayland + screen and system audio, encodes video to JPEG, and streams them over TCP + so OBS with the obs-teleport plugin can discover and display the stream. + . + Provides a headless CLI (teleportfling) and a desktop GUI with system tray + (teleportfling-gui). +Homepage: http://homeserver:3050/pedley/TeleportFling +EOF + +echo "Building .deb..." +nix shell nixpkgs#dpkg --command dpkg-deb --build --root-owner-group "${ROOT}" "${OUT}/${PKG_NAME}_${VERSION}_${ARCH}.deb" + +echo "Done: ${OUT}/${PKG_NAME}_${VERSION}_${ARCH}.deb" \ No newline at end of file diff --git a/todo.md b/todo.md index 3590a06..8c8965e 100644 --- a/todo.md +++ b/todo.md @@ -18,10 +18,18 @@ Backlog of deferred / planned work, tracked outside of milestone milestones. systemd unit (`contrib/teleportfling.service`) that starts the headless CLI with its own config at `~/.config/teleportfling/daemon.json`. -## Ideas (not yet scoped) +## Future / Ideas (not yet scoped) -- Live settings changes mid-stream (`flinger.SetConfig` for FPS/quality). -- Bitrate/bandwidth tuning beyond the balanced 1080p30 default. -- Screen capture scaling / downsampling. -- Packaging (Nix package / AppImage / release binaries) — deliberately - deferred. \ No newline at end of file +- [ ] **Live settings changes mid-stream** — `flinger.SetConfig` for FPS/quality + without a restart. +- [ ] **Bitrate / bandwidth tuning** — options beyond the balanced 1080p30 + default. +- [ ] **Screen capture scaling / downsampling** — capture a scaled region rather + than the monitor's native resolution. +- [ ] **Multi-monitor verification** — the GUI monitor picker enumerates via + hyprctl; confirm behaviour on a real multi-monitor setup. +- [ ] **Non-Hyprland monitor enumeration** — portal-based fallback so the picker + works on GNOME/KDE etc. (currently falls back to a single indexed option). +- [ ] **Packaging** — Nix flake package, AppImage, and `.deb` for Debian/Ubuntu + (in progress: flake package, AppImage and .deb scripts added under + packaging/; verify on a real Debian/Ubuntu machine). \ No newline at end of file