#!/usr/bin/env bash # # Fluxer Flatpak installer — install from the .flatpakref + instance URL + # GPU-aware flags. Works on any distro with Flatpak (the fallback when there # is no native package). # # Usage: # INSTANCE=https://fluxer.example.com ./install-fluxer-flatpak.sh # # Env: # EDITION fluxer (only stable is published as flatpak; default: fluxer) # INSTANCE your instance base URL (prompted if unset) # DESKTOP_ONLY=1 skip install, only rewrite the .desktop entry # ASSUME_YES=1 never prompt (passes -y --noninteractive to flatpak) # FLATPAK_USER=1 install into the user installation instead of system # GPU force detection: nvidia | intel | amd | none # SESSION force session type: wayland | x11 # HW_ENCODE=1 enable hardware video *encode* (Intel/AMD only; default 0: # upstream Chromium on Linux rarely delivers it, decode is # what matters for watching streams) # DISABLE_GPU_SANDBOX=1 pass --disable-gpu-sandbox (NVIDIA only; weakens the # Chromium sandbox, so it is OFF by default — but note the # sandbox can block /dev/nvidia* and force software GL) # ALLOW_HTTP=1 allow a plain http:// instance URL (loopback is always allowed) # WM_CLASS override StartupWMClass in the .desktop entry # VERIFY=1 run the launch-based flag check (opens the app briefly) # EXTRA_FLAGS extra flags, space-separated, restricted charset. # --enable-features= / --disable-features= are MERGED into # ours (Chromium keeps only the last occurrence of each). # # NOTE: --fluxer-app-url is not documented publicly (checked Sep 2026). # Only point the desktop app at an instance you control. # # NOTE on GPU decode inside Flatpak: VA-API also needs the matching # org.freedesktop.Platform ffmpeg/GL extensions, which this script does not # manage — the flags below are necessary but may not be sufficient. # set -euo pipefail EDITION="${EDITION:-fluxer}" INSTANCE="${INSTANCE:-https://fluxer.systux.xyz}" APP_ID='app.fluxer.Fluxer' REF_URL='https://pkgs.fluxer.com/flatpak/fluxer.flatpakref' HAVE_FLATPAK=0 command -v flatpak >/dev/null && HAVE_FLATPAK=1 info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } ok() { printf '\033[1;32m ✓\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m !\033[0m %s\n' "$*"; } die() { printf '\033[1;31m ✗\033[0m %s\n' "$*" >&2; exit 1; } confirm() { # confirm "question" -> default yes; non-interactive => yes only with ASSUME_YES [[ "${ASSUME_YES:-0}" == 1 ]] && return 0 [[ -t 0 ]] || return 1 local ans; read -rp "$1 [Y/n] " ans [[ ! "$ans" =~ ^[Nn] ]] } join_by() { local IFS="$1"; shift; printf '%s' "$*"; } FLATPAK_SCOPE=() if [[ "${FLATPAK_USER:-0}" == 1 ]]; then FLATPAK_SCOPE+=(--user); fi FLATPAK_FLAGS=() if [[ "${ASSUME_YES:-0}" == 1 ]]; then FLATPAK_FLAGS+=(-y --noninteractive); fi # ------------------------------------------------------------ preflight (( EUID != 0 )) || die "run as your normal user, not root" [[ "$EDITION" == fluxer ]] \ || die "only stable 'fluxer' is published as a flatpak (got '$EDITION')" if [[ -z "$INSTANCE" ]]; then [[ -t 0 ]] || die "INSTANCE not set and no terminal to prompt on" read -rp "Instance URL (e.g. https://fluxer.example.com): " INSTANCE fi INSTANCE="${INSTANCE%/}" # Restricted charset: keeps the .desktop Exec= line free of characters that # would need escaping/quoting under the Desktop Entry spec. No userinfo (@), # no query/fragment, no IPv6 literals. URL_RE='^(https?)://([A-Za-z0-9._-]+)(:[0-9]+)?(/[A-Za-z0-9._/-]*)?$' [[ "$INSTANCE" =~ $URL_RE ]] || die "invalid instance URL: '$INSTANCE'" URL_SCHEME="${BASH_REMATCH[1]}" URL_HOST="${BASH_REMATCH[2]}" if [[ "$URL_SCHEME" == http && "${ALLOW_HTTP:-0}" != 1 ]]; then [[ "$URL_HOST" =~ ^(localhost|127\.[0-9]+\.[0-9]+\.[0-9]+)$ ]] \ || die "plain http:// is only accepted for loopback; use https:// or set ALLOW_HTTP=1" fi info "Checking $INSTANCE …" if curl -fsS -o /dev/null --max-time 10 "$INSTANCE/"; then ok "$INSTANCE reachable" else warn "$INSTANCE did not answer — continuing anyway (server may be down)" fi # ----------------------------------------------------------------- install if [[ "${DESKTOP_ONLY:-0}" != 1 ]]; then (( HAVE_FLATPAK )) || die "flatpak not found — install it first (e.g. sudo apt install flatpak / sudo dnf install flatpak)" if flatpak info "${FLATPAK_SCOPE[@]}" "$APP_ID" &>/dev/null; then ok "$APP_ID already installed" else info "Installing $APP_ID from $REF_URL …" flatpak install "${FLATPAK_SCOPE[@]}" "${FLATPAK_FLAGS[@]}" "$REF_URL" ok "$APP_ID installed" fi fi # ------------------------------------------------- detect session + GPU if [[ -n "${SESSION:-}" ]]; then [[ "$SESSION" =~ ^(wayland|x11)$ ]] || die "SESSION must be wayland | x11 (got '$SESSION')" else SESSION="${XDG_SESSION_TYPE:-unknown}" if [[ -n "${WAYLAND_DISPLAY:-}" ]]; then SESSION=wayland; fi if [[ "$SESSION" != wayland && "$SESSION" != x11 ]]; then warn "session type is '$SESSION' (TTY/SSH?) — assuming x11-style minimal flags; set SESSION=wayland|x11 to override" SESSION=x11 fi fi info "Session type: $SESSION" # Read vendor IDs straight from sysfs: no lspci dependency, and (unlike # `lspci | grep intel`) it only looks at actual GPUs, not every Intel chipset. IS_NVIDIA=0 IS_INTEL=0 IS_AMD=0 for f in /sys/class/drm/card*/device/vendor; do [[ -r "$f" ]] || continue case "$(<"$f")" in 0x10de) IS_NVIDIA=1 ;; 0x8086) IS_INTEL=1 ;; 0x1002) IS_AMD=1 ;; esac done if [[ -n "${GPU:-}" ]]; then IS_NVIDIA=0 IS_INTEL=0 IS_AMD=0 case "$GPU" in nvidia) IS_NVIDIA=1 ;; intel) IS_INTEL=1 ;; amd) IS_AMD=1 ;; none) ;; *) die "GPU must be nvidia | intel | amd | none (got '$GPU')" ;; esac fi info "GPU: nvidia=$IS_NVIDIA intel=$IS_INTEL amd=$IS_AMD" if (( IS_NVIDIA && IS_INTEL )); then warn "hybrid Intel+NVIDIA detected — using the NVIDIA path; override with GPU=intel if the iGPU renders" fi if (( IS_NVIDIA )); then warn "flatpak + NVIDIA: VA-API additionally needs the matching org.freedesktop.Platform ffmpeg/GL extensions — flags alone may not enable hardware decode" fi # ------------------------------------------------- build flag set # Collect features once and emit a single --enable-features / --disable-features: # Chromium doesn't merge repeated flags, the last one wins. # # Feature names follow Chromium >= 131 (Vaapi* decode features were renamed # Accelerated*). Hardware decode is on by default on Wayland from Chromium # 143, so AcceleratedVideoDecodeLinuxGL is belt-and-braces there. # nvidia-vaapi-driver is decode-only, so no encoder feature on NVIDIA. FLAGS=("--fluxer-app-url=${INSTANCE}" --ozone-platform-hint=auto) ENABLE=() DISABLE=() PREFIX="" if (( IS_NVIDIA )); then # VaapiOnNvidiaGPUs: Chromium skips nvidia-drm for VA-API unless it is set # (crbug 1492880 — verified in vaapi_wrapper.cc, the "Should skip nVidia # device" warning disappears with it). ENABLE+=(AcceleratedVideoDecodeLinuxGL VaapiOnNvidiaGPUs) FLAGS+=(--use-gl=angle --use-angle=gl --ignore-gpu-blocklist --disable-gpu-driver-bug-workarounds) # libva must be told which driver to load at *launch*, not just for vainfo. PREFIX="env LIBVA_DRIVER_NAME=nvidia " if [[ "${DISABLE_GPU_SANDBOX:-0}" == 1 ]]; then warn "DISABLE_GPU_SANDBOX=1 — the Chromium GPU sandbox will be disabled" FLAGS+=(--disable-gpu-sandbox) else warn "GPU sandbox left enabled — if video stays software-decoded, retry with DISABLE_GPU_SANDBOX=1 (it can block /dev/nvidia*)" fi elif (( IS_INTEL || IS_AMD )); then ENABLE+=(AcceleratedVideoDecodeLinuxGL) if [[ "${HW_ENCODE:-0}" == 1 ]]; then ENABLE+=(AcceleratedVideoEncoder); fi FLAGS+=(--ignore-gpu-blocklist) fi if [[ "$SESSION" == wayland ]]; then ENABLE+=(WebRTCPipeWireCapturer) warn "Wayland screen sharing inside Flatpak needs PipeWire + xdg-desktop-portal (usually present on modern desktops)" else info "X11 session — no PipeWire forcing (native screen capture)" fi # EXTRA_FLAGS: merge feature lists instead of appending a second switch that # would silently override ours. if [[ -n "${EXTRA_FLAGS:-}" ]]; then read -ra extra <<<"$EXTRA_FLAGS" for tok in "${extra[@]}"; do [[ "$tok" =~ ^[A-Za-z0-9._:/=,+-]+$ ]] \ || die "EXTRA_FLAGS token can't be used in a .desktop Exec line: '$tok'" case "$tok" in --enable-features=*) IFS=, read -ra t <<<"${tok#*=}"; ENABLE+=("${t[@]}") ;; --disable-features=*) IFS=, read -ra t <<<"${tok#*=}"; DISABLE+=("${t[@]}") ;; *) FLAGS+=("$tok") ;; esac done fi if (( ${#ENABLE[@]} )); then FLAGS+=("--enable-features=$(join_by , "${ENABLE[@]}")"); fi if (( ${#DISABLE[@]} )); then FLAGS+=("--disable-features=$(join_by , "${DISABLE[@]}")"); fi RUN="flatpak run $APP_ID" LAUNCH="${PREFIX}${RUN} ${FLAGS[*]}" info "Launch command: $LAUNCH" # ------------------------------------------------- write .desktop override # Flatpak exports its own .desktop file, but its Exec= line (flatpak run # --branch=… --command=…) can't be rewritten by command-word substitution, so # we always write a fresh override that carries our flags. Same filename as # the exported entry, so it wins over it. DESKTOP_DIR="$HOME/.local/share/applications" DESKTOP_FILE="$DESKTOP_DIR/$APP_ID.desktop" mkdir -p "$DESKTOP_DIR" APP_NAME="Fluxer" ICON="$APP_ID" for cand in "$HOME/.local/share/flatpak/exports/share/applications/$APP_ID.desktop" \ "/var/lib/flatpak/exports/share/applications/$APP_ID.desktop"; do if [[ -r "$cand" ]]; then n="$(grep -m1 -E '^Name=' "$cand" | cut -d= -f2-)" i="$(grep -m1 -E '^Icon=' "$cand" | cut -d= -f2-)" [[ -n "$n" ]] && APP_NAME="$n" [[ -n "$i" ]] && ICON="$i" break fi done { cat <"$DESKTOP_FILE" ok "wrote $DESKTOP_FILE (overrides the flatpak-exported entry)" update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true if command -v desktop-file-validate >/dev/null; then if desktop-file-validate "$DESKTOP_FILE"; then ok "desktop entry validates" else warn "desktop-file-validate reported issues (see above)"; fi fi # ------------------------------------------------- verify flags exist if [[ "${VERIFY:-0}" == 1 ]]; then if pgrep -f "flatpak.*$APP_ID" >/dev/null 2>&1; then warn "$APP_ID is already running (single-instance) — skipping the launch-based check" else dbg="$(timeout 15 flatpak run "$APP_ID" "--fluxer-app-url=${INSTANCE}" --fluxer-debug-info 2>&1 || true)" if grep -qF -- "$INSTANCE" <<<"$dbg"; then ok "app reports the instance override: $INSTANCE" else warn "could not confirm the override from --fluxer-debug-info output" fi fi else info "skipping launch-based check by default on flatpak (it would open the app window); VERIFY=1 to force" fi echo ok "Done. Quit any running Fluxer first (tray → Quit, single-instance!), then launch from the app menu."