56e0d3662b
The Raspberry Pi Zero 2 W (512 MB RAM) ran out of memory compiling signaling.cpp — nlohmann/json peaks well above available RAM at -O3, and ninja runs 6 parallel jobs on a quad-core — so the OOM killer terminated the compiler with 'Killed signal terminated program cc1plus'. When less than 1.5 GB RAM is detected, the install script now adds 1 GB of temporary build-time swap (fallocate with a dd fallback, removed on exit; some filesystems such as btrfs refuse swapfiles outright, in which case it warns and continues) and compiles with a single job. The cleanup trap also covers the SDL3 source directory. The swap mechanics were live-tested with a small swapfile (swapon/ swapoff lifecycle); this dev machine's filesystem rejects swapfiles, which is what exposed the need for the warning fallback. The Pi's ext4 rootfs accepts them.
198 lines
8.1 KiB
Bash
Executable File
198 lines
8.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# install-receiver.sh — build and install the screencast RECEIVER on a
|
|
# small ARM board (e.g. a Raspberry Pi Zero 2 W).
|
|
#
|
|
# Why a separate script: the sender pulls in PipeWire and the
|
|
# xdg-desktop-portal capture stack, which a headless receiver board neither
|
|
# has nor needs. This script configures the build with -Dsender=false so
|
|
# only the receiver binary is compiled.
|
|
#
|
|
# Usage (from the repository root, on the target machine):
|
|
#
|
|
# sudo ./scripts/install-receiver.sh
|
|
#
|
|
# Environment overrides:
|
|
# SC_RECEIVER_INSTALL_DIR install target (default /usr/local/bin)
|
|
# SC_RECEIVER_SKIP_TESTS set to 1 to skip the test suite (faster install)
|
|
# SC_RECEIVER_SERVICE set to 0 to skip the systemd service setup
|
|
#
|
|
# Requirements:
|
|
# - Debian/Raspberry Pi OS (apt) with GCC 13 or newer (the code uses
|
|
# C++20 <format>; Raspberry Pi OS Trixie or newer ships a suitable GCC)
|
|
# - a running avahi-daemon (installed and enabled by this script)
|
|
# - a graphical session or KMS console for the receiver window
|
|
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
BUILD_DIR="${REPO_ROOT}/build-receiver"
|
|
INSTALL_DIR="${SC_RECEIVER_INSTALL_DIR:-/usr/local/bin}"
|
|
SKIP_TESTS="${SC_RECEIVER_SKIP_TESTS:-0}"
|
|
ENABLE_SERVICE="${SC_RECEIVER_SERVICE:-1}"
|
|
|
|
log() { printf '\n=== %s\n' "$*"; }
|
|
die() { printf 'install-receiver: error: %s\n' "$*" >&2; exit 1; }
|
|
|
|
TEMP_SWAP=""
|
|
SDL_DIR=""
|
|
cleanup() {
|
|
if [ -n "${TEMP_SWAP}" ]; then
|
|
swapoff "${TEMP_SWAP}" 2>/dev/null || true
|
|
rm -f "${TEMP_SWAP}"
|
|
fi
|
|
if [ -n "${SDL_DIR}" ]; then
|
|
rm -rf "${SDL_DIR}"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
[ "$(id -u)" = 0 ] || die "run with sudo (installs to ${INSTALL_DIR})"
|
|
[ -f "${REPO_ROOT}/meson.build" ] || die "run from the screen_cast repository"
|
|
command -v apt-get >/dev/null 2>&1 || die "this script supports apt-based systems only"
|
|
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
|
|
# --- build and runtime dependencies -----------------------------------------
|
|
log "installing build and runtime dependencies (this may take a while)"
|
|
apt-get update -qq || apt-get update
|
|
apt-get install -y --no-install-recommends \
|
|
build-essential \
|
|
meson \
|
|
ninja-build \
|
|
pkg-config \
|
|
libavcodec-dev \
|
|
libavutil-dev \
|
|
libswscale-dev \
|
|
nlohmann-json3-dev \
|
|
libavahi-client-dev \
|
|
avahi-daemon \
|
|
ca-certificates
|
|
|
|
# The receiver renders with SDL3. Older releases do not package it; build a
|
|
# minimal SDL3 from source in that case.
|
|
if ! apt-get install -y --no-install-recommends libsdl3-dev; then
|
|
log "libsdl3-dev is not packaged; building SDL3 from source (15-30 min on small boards)"
|
|
apt-get install -y --no-install-recommends cmake git
|
|
SDL_DIR="$(mktemp -d)"
|
|
git clone --depth 1 --branch release-3.2.x \
|
|
https://github.com/libsdl-org/SDL.git "${SDL_DIR}/SDL"
|
|
cmake -S "${SDL_DIR}/SDL" -B "${SDL_DIR}/build" \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DSDL_TESTS=OFF \
|
|
-DSDL_INSTALL_DOCS=OFF \
|
|
-DSDL_EXAMPLES=OFF
|
|
cmake --build "${SDL_DIR}/build" --parallel "$(nproc)"
|
|
cmake --install "${SDL_DIR}/build"
|
|
fi
|
|
|
|
# --- compiler sanity check ---------------------------------------------------
|
|
log "checking the compiler for C++20 <format> support"
|
|
if ! printf '#include <format>\nint main() { return 0; }\n' |
|
|
g++ -std=c++20 -x c++ - -o /dev/null 2>/dev/null; then
|
|
die "g++ lacks C++20 <format>; install GCC 13 or newer (Raspberry Pi OS \
|
|
Trixie or newer ships one). Aborting before a long build."
|
|
fi
|
|
|
|
# --- memory-constrained boards ----------------------------------------------
|
|
# The signaling unit includes nlohmann/json.hpp, which can peak well above
|
|
# the RAM of small boards; the OOM killer then terminates the compiler
|
|
# ("Killed signal terminated program cc1plus"). Give such boards temporary
|
|
# swap and a single compile job.
|
|
BUILD_JOBS="$(nproc)"
|
|
MEM_TOTAL_KB="$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)"
|
|
if [ "${MEM_TOTAL_KB}" -lt 1500000 ]; then
|
|
SWAP_TOTAL_KB="$(awk '/^SwapTotal:/ {print $2}' /proc/meminfo)"
|
|
if [ "${SWAP_TOTAL_KB}" -lt 1000000 ]; then
|
|
FREE_KB="$(df -k --output=avail / | awk 'NR==2 {print $1}')"
|
|
if [ "${FREE_KB}" -lt 1572864 ]; then
|
|
die "board has little RAM and not enough free disk for 1 GB of temporary build swap; build on a larger machine or add swap manually"
|
|
fi
|
|
log "low-memory board: adding 1 GB of temporary swap for the build (removed afterwards)"
|
|
TEMP_SWAP="/var/tmp/screencast-build.swap"
|
|
if ! (fallocate -l 1G "${TEMP_SWAP}" 2>/dev/null ||
|
|
dd if=/dev/zero of="${TEMP_SWAP}" bs=1M count=1024 status=none); then
|
|
die "failed to write the swapfile"
|
|
fi
|
|
chmod 600 "${TEMP_SWAP}"
|
|
if ! mkswap "${TEMP_SWAP}" >/dev/null 2>&1 || ! swapon "${TEMP_SWAP}" 2>/dev/null; then
|
|
# Some filesystems (e.g. btrfs) refuse swapfiles outright.
|
|
rm -f "${TEMP_SWAP}"
|
|
TEMP_SWAP=""
|
|
log "WARNING: could not activate a swapfile on this filesystem; continuing with a single compile job — the build may still run out of memory"
|
|
fi
|
|
fi
|
|
BUILD_JOBS=1
|
|
log "low-memory board: compiling with a single job"
|
|
fi
|
|
|
|
# --- build the receiver ------------------------------------------------------
|
|
log "configuring a receiver-only build in ${BUILD_DIR}"
|
|
rm -rf "${BUILD_DIR}"
|
|
meson setup "${BUILD_DIR}" "${REPO_ROOT}" -Dsender=false
|
|
|
|
log "compiling (this takes a while on small boards)"
|
|
meson compile -C "${BUILD_DIR}" --jobs "${BUILD_JOBS}"
|
|
|
|
if [ "${SKIP_TESTS}" != "1" ]; then
|
|
log "running the test suite"
|
|
meson test -C "${BUILD_DIR}" --print-errorlogs
|
|
fi
|
|
|
|
# --- install -----------------------------------------------------------------
|
|
log "installing the receiver to ${INSTALL_DIR}"
|
|
install -m 755 "${BUILD_DIR}/src/app/screencast" "${INSTALL_DIR}/screencast"
|
|
|
|
systemctl enable --now avahi-daemon 2>/dev/null || true
|
|
|
|
# --- systemd autostart --------------------------------------------------------
|
|
if [ "${ENABLE_SERVICE}" = "1" ] && command -v systemctl >/dev/null 2>&1; then
|
|
log "installing the systemd service (autostart on boot)"
|
|
RUN_USER="${SUDO_USER:-root}"
|
|
|
|
# Headless KMSDRM rendering needs device access; skip groups that do
|
|
# not exist on this system.
|
|
if [ "${RUN_USER}" != "root" ] && id "${RUN_USER}" >/dev/null 2>&1; then
|
|
for group in video render input; do
|
|
if getent group "${group}" >/dev/null 2>&1; then
|
|
usermod -aG "${group}" "${RUN_USER}" || true
|
|
fi
|
|
done
|
|
fi
|
|
|
|
sed -e "s|__SC_RECEIVER_BIN__|${INSTALL_DIR}/screencast|g" \
|
|
-e "s|__SC_RECEIVER_USER__|${RUN_USER}|g" \
|
|
"${REPO_ROOT}/systemd/screencast-receiver.service" \
|
|
> /etc/systemd/system/screencast-receiver.service
|
|
chmod 644 /etc/systemd/system/screencast-receiver.service
|
|
systemctl daemon-reload
|
|
systemctl enable --now screencast-receiver.service
|
|
else
|
|
log "skipping the systemd service (disabled or systemd not available)"
|
|
fi
|
|
|
|
log "done."
|
|
cat <<'EOF'
|
|
|
|
The receiver is installed and starts automatically at boot.
|
|
|
|
systemctl status screencast-receiver # is it running?
|
|
journalctl -u screencast-receiver -f # follow its logs
|
|
sudo systemctl stop screencast-receiver # stop gracefully (Ctrl-C like)
|
|
sudo systemctl disable --now screencast-receiver # turn autostart off
|
|
|
|
Notes for small boards (e.g. Pi Zero 2 W):
|
|
- No window manager is required: the receiver renders directly to the
|
|
kernel display pipeline (SDL KMSDRM). Use Raspberry Pi OS Lite with
|
|
Console Autologin (raspi-config) and no desktop enabled — a running
|
|
compositor would own the display and the receiver could not start.
|
|
- Disable console blanking so the picture never goes dark: add
|
|
consoleblank=0 to /boot/firmware/cmdline.txt and reboot.
|
|
- Decoding is software H.264; expect smooth playback for small streams
|
|
and a lower frame rate at high resolutions.
|
|
- Stop the receiver via systemctl (never kill -9; stale mDNS records
|
|
would break discovery for ~75 minutes).
|
|
- On a desktop session instead, edit the installed service file and
|
|
uncomment the Environment lines for XDG_RUNTIME_DIR/WAYLAND_DISPLAY.
|
|
- avahi-daemon must stay enabled; it is what senders discover.
|
|
EOF |