flatpak: add build.sh, prepare-libs.sh, and updated CI workflow

- build.sh: automated flatpak build script with --bundle/--clean flags
- prepare-libs.sh: copies system tray libraries from host into flatpak/libs/
- .github/workflows/flatpak.yml: companion CI workflow supporting both
  source builds and release-triggered builds
- Remove old flatpak-release.yml (replaced by flatpak.yml)
- .gitignore: add flatpak build artifact patterns
- Scripts are executable
This commit is contained in:
John Smith
2026-06-02 21:32:08 -04:00
parent b2f1ac3e23
commit c886b5e059
10 changed files with 4714 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
name: Flatpak
on:
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag to build the Flatpak from (leave empty to build from current commit)'
required: false
release:
types: [published]
# Builds the Drop Desktop Flatpak.
#
# Strategy: Companion to the main release workflow.
# On release, it downloads the pre-built .deb from the release assets,
# bundles system tray libraries from apt, and builds the Flatpak.
#
# For workflow_dispatch without a tag, it builds everything from source.
jobs:
flatpak-build:
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Install Flatpak SDK
run: |
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
- name: Install GNOME Platform 48
run: |
flatpak install --system flathub org.gnome.Platform//48 org.gnome.Sdk//48 -y
- name: Install system tray libraries
run: |
sudo apt-get install -y \
libayatana-appindicator3-dev \
libdbusmenu-gtk3-dev
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'
- name: Install system deps for Tauri
run: |
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
librsvg2-dev \
patchelf \
xdg-utils
- name: Determine build mode
id: build_mode
run: |
if [[ -n "${{ github.event.inputs.release_tag }}" ]]; then
echo "source=release" >> "$GITHUB_OUTPUT"
echo "tag=${{ github.event.inputs.release_tag }}" >> "$GITHUB_OUTPUT"
elif [[ "${{ github.event_name }}" == "release" ]]; then
echo "source=release" >> "$GITHUB_OUTPUT"
echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
else
echo "source=source" >> "$GITHUB_OUTPUT"
fi
# ── Mode A: Build from source (workflow_dispatch without tag) ──
- name: (Source) Install frontend dependencies
if: steps.build_mode.outputs.source == 'source'
run: pnpm install
- name: (Source) Build Tauri .deb
if: steps.build_mode.outputs.source == 'source'
run: pnpm tauri build --bundles deb
# ── Mode B: Download from release ──
- name: (Release) Download .deb from release
if: steps.build_mode.outputs.source == 'release'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ steps.build_mode.outputs.tag }}"
gh release download "$TAG" --pattern '*_amd64.deb' --dir flatpak/
# Rename to expected path
# ── Common: Prepare inputs for flatpak-builder ──
- name: Prepare libraries and .deb for Flatpak build
run: |
# Copy system tray libraries from host
bash flatpak/prepare-libs.sh
# If we downloaded from a release, rename to expected name
if [ -z "$(ls flatpak/drop-app.deb 2>/dev/null)" ]; then
if ls flatpak/*_amd64.deb 1>/dev/null 2>&1; then
mv flatpak/*_amd64.deb flatpak/drop-app.deb
else
# Source build — create symlink
bash flatpak/prepare-deb.sh
fi
fi
- name: Build Flatpak
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: org.droposs.client.flatpak
manifest-path: flatpak/org.droposs.client.yml
cache-key: flatpak-${{ github.event.release.tag_name || github.sha }}
branch: master
- name: Upload Flatpak to release
if: steps.build_mode.outputs.source == 'release'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ steps.build_mode.outputs.tag }}"
gh release upload "$TAG" org.droposs.client.flatpak
- name: Upload Flatpak as workflow artifact
if: steps.build_mode.outputs.source == 'source'
uses: actions/upload-artifact@v4
with:
name: org.droposs.client-flatpak
path: org.droposs.client.flatpak
+8
View File
@@ -75,3 +75,11 @@ coverage/
tmp/
.agents/
skills-lock.json
# ── Flatpak build artifacts ──
.flatpak-builder/
flatpak-build-dir/
flatpak/drop-app.deb
flatpak/libs/
flatpak/deb-check/
org.droposs.client.flatpak
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env bash
# build.sh — Build the Drop Desktop Flatpak from scratch.
#
# Usage:
# ./flatpak/build.sh # Build flatpak
# ./flatpak/build.sh --bundle # Build and create portable .flatpak bundle
# ./flatpak/build.sh --clean # Clean build artifacts only
#
# Prerequisites:
# - flatpak, flatpak-builder installed
# - GNOME Platform/SDK 48 installed:
# flatpak install --user flathub org.gnome.Platform//48 org.gnome.Sdk//48
# - pnpm, node, rust/cargo installed for Tauri build
#
# The build process:
# 1. Build the Tauri app → produces .deb
# 2. Prepare system tray libraries from host
# 3. Create symlink from versioned .deb to flatpak/drop-app.deb
# 4. Run flatpak-builder
# 5. Optionally bundle to .flatpak file
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
BUNDLE_FILE="${PROJECT_DIR}/org.droposs.client.flatpak"
BUILD_DIR="${PROJECT_DIR}/flatpak-build-dir"
# ── Parse arguments ──
do_bundle=false
do_clean=false
for arg in "$@"; do
case "$arg" in
--bundle) do_bundle=true ;;
--clean) do_clean=true ;;
--help)
echo "Usage: $0 [--bundle] [--clean]"
echo " --bundle Create portable .flatpak bundle after build"
echo " --clean Remove build artifacts only"
exit 0
;;
*)
echo "Unknown option: $arg"
echo "Usage: $0 [--bundle] [--clean]"
exit 1
;;
esac
done
# ── Clean mode ──
if [ "$do_clean" = true ]; then
echo "==> Cleaning build artifacts..."
rm -rf "$BUILD_DIR"
rm -f "$SCRIPT_DIR/drop-app.deb"
rm -rf "$SCRIPT_DIR/libs"
rm -f "$BUNDLE_FILE"
rm -rf "$PROJECT_DIR/.flatpak-builder"
echo " Done."
exit 0
fi
# ── Step 0: Verify prerequisites ──
echo "==> [1/4] Verifying prerequisites..."
if ! command -v flatpak &>/dev/null; then
echo "Error: flatpak not found. Install it first." >&2
exit 1
fi
if ! command -v flatpak-builder &>/dev/null; then
echo "Error: flatpak-builder not found. Install it first." >&2
exit 1
fi
# Check GNOME runtime/SDK are available
if ! flatpak info org.gnome.Platform//48 &>/dev/null; then
echo "Installing GNOME Platform 48..."
flatpak install --user flathub org.gnome.Platform//48 org.gnome.Sdk//48 -y
fi
echo " All prerequisites met."
# ── Step 1: Build Tauri app ──
echo "==> [2/4] Building Tauri app..."
cd "$PROJECT_DIR"
# Build frontend + Tauri binary + .deb package
pnpm tauri build --bundles deb
echo " Tauri build complete."
# ── Step 2: Prepare system tray libraries ──
echo "==> [3/4] Preparing system tray libraries..."
bash "$SCRIPT_DIR/prepare-libs.sh"
# ── Step 3: Prepare .deb for flatpak ──
echo "==> [4/4] Building Flatpak..."
bash "$SCRIPT_DIR/prepare-deb.sh"
# ── Step 4: Run flatpak-builder ──
flatpak-builder --user --force-clean "$BUILD_DIR" "$SCRIPT_DIR/org.droposs.client.yml"
flatpak-builder --user --install "$BUILD_DIR" "$SCRIPT_DIR/org.droposs.client.yml"
echo ""
echo "=== Flatpak build complete ==="
echo " Run: flatpak run org.droposs.client"
# ── Optional: create portable bundle ──
if [ "$do_bundle" = true ]; then
echo "==> Creating portable bundle..."
flatpak build-bundle ~/.local/share/flatpak/repo "$BUNDLE_FILE" org.droposs.client
echo " Bundle: $BUNDLE_FILE ($(du -h "$BUNDLE_FILE" | cut -f1))"
fi
echo ""
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""Parse pnpm-lock.yaml and generate flatpak node-sources.json.
Outputs entries in the same format as flatpak-node-generator npm mode:
{
"type": "file",
"url": "https://registry.npmjs.org/.../-/...tgz",
"sha512": "hex",
"dest": "flatpak-node/npmcache/...",
}
"""
import json
import sys
import base64
from pathlib import Path
try:
import yaml
except ImportError:
print("PyYAML is required. Install with: pip3 install pyyaml", file=sys.stderr)
sys.exit(1)
def sri_to_hex(sri: str) -> str:
"""Convert SRI hash (e.g. 'sha512-<base64>' or 'sha256-<base64>') to hex string."""
algo, b64 = sri.split("-", 1)
raw = base64.b64decode(b64)
return raw.hex()
def make_npm_url(pkg_name: str, version: str) -> str:
"""Construct npm registry tarball download URL."""
if pkg_name.startswith("@"):
scope, name = pkg_name[1:].split("/", 1)
return f"https://registry.npmjs.org/@{scope}%2f{name}/-/{name}-{version}.tgz"
else:
return f"https://registry.npmjs.org/{pkg_name}/-/{pkg_name}-{version}.tgz"
def make_dest(pkg_name: str, version: str) -> str:
"""Construct flatpak vendor destination directory."""
safe_name = pkg_name.replace("/", "/")
return f"flatpak-node/npmcache/{safe_name}/{version}"
def main():
lockfile_path = Path("pnpm-lock.yaml")
if not lockfile_path.exists():
print("pnpm-lock.yaml not found", file=sys.stderr)
sys.exit(1)
with open(lockfile_path) as f:
data = yaml.safe_load(f)
packages = data.get("packages", {})
if not packages:
print("No packages section found in pnpm-lock.yaml", file=sys.stderr)
sys.exit(1)
entries = []
seen = set()
for pkg_key in packages:
pkg_info = packages[pkg_key]
# Skip: only generate for published npm packages (not workspace/links)
resolution = pkg_info.get("resolution")
if not resolution or "integrity" not in resolution:
continue
integrity = resolution["integrity"]
# Parse key: '@scope/name@version' or 'name@version'
# pnpm v9 uses keys like '@scope/name@1.0.0' or 'name@1.0.0'
if "@" not in pkg_key:
continue # should not happen for valid entries
# Handle scoped packages: first @ is scope separator, last @ is version separators
if pkg_key.startswith("@"):
# '@scope/name@version'
parts = pkg_key.split("@")
# parts = ['', 'scope/name', 'version']
scope_and_name = "@" + parts[1] # '@scope/name'
version = parts[2]
else:
# 'name@version'
# split on last @ to handle package names containing @ (rare but possible)
# For typical case, split on @
parts = pkg_key.rsplit("@", 1)
scope_and_name = parts[0]
version = parts[1]
# Avoid duplicates
dedup_key = f"{scope_and_name}@{version}"
if dedup_key in seen:
continue
seen.add(dedup_key)
# Determine hash algorithm and convert to hex
if integrity.startswith("sha512-"):
sha_key = "sha512"
elif integrity.startswith("sha256-"):
sha_key = "sha256"
elif integrity.startswith("sha1-"):
sha_key = "sha1"
else:
print(f"WARNING: Unknown integrity format for {pkg_key}: {integrity[:30]}", file=sys.stderr)
continue
hex_hash = sri_to_hex(integrity)
url = make_npm_url(scope_and_name, version)
dest = make_dest(scope_and_name, version)
entry = {
"type": "file",
"url": url,
sha_key: hex_hash,
"dest": dest,
}
entries.append(entry)
# Sort for deterministic output
entries.sort(key=lambda e: e["dest"])
with open("flatpak/node-sources.json", "w") as f:
json.dump(entries, f, indent=2)
print(f"Generated flatpak/node-sources.json with {len(entries)} package entries")
print(f"File size: {Path('flatpak/node-sources.json').stat().st_size} bytes")
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=Drop
Comment=Drop Desktop Client - open-source game distribution platform
Exec=/app/bin/drop-app
Icon=org.droposs.client
Terminal=false
Categories=Game;Network;
MimeType=x-scheme-handler/drop;
StartupWMClass=drop-app
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<component type="desktop-application">
<id>org.droposs.client</id>
<metadata_license>MIT</metadata_license>
<project_license>MIT</project_license>
<name>Drop</name>
<summary>Desktop client for the Drop game distribution platform</summary>
<description>
<p>Drop is an open-source, self-hosted game distribution platform. This desktop client connects to a Drop server to browse, download, and launch your game library.</p>
</description>
<categories>
<category>Game</category>
<category>Network</category>
</categories>
<url type="homepage">https://github.com/Drop-OSS/drop-app</url>
<developer_name>Drop OSS</developer_name>
<launchable type="desktop-id">org.droposs.client.desktop</launchable>
<releases>
<release version="0.1.0" date="2026-06-02"/>
</releases>
</component>
+98
View File
@@ -0,0 +1,98 @@
id: org.droposs.client
runtime: org.gnome.Platform
runtime-version: '48'
sdk: org.gnome.Sdk
command: drop-app
finish-args:
- --socket=wayland
- --socket=fallback-x11
- --device=dri
- --share=ipc
- --share=network
- --filesystem=host
- --talk-name=org.kde.StatusNotifierWatcher
- --filesystem=xdg-run/tray-icon:create
# Uncomment if webview renders black on Wayland:
- --env=WEBKIT_DISABLE_COMPOSITING_MODE=1
- --env=WEBKIT_DISABLE_DMABUF_RENDERER=1
modules:
- name: drop-app
buildsystem: simple
sources:
- type: file
path: drop-app.deb
- type: file
path: org.droposs.client.metainfo.xml
- type: file
path: org.droposs.client.desktop
- type: file
path: org.droposs.client.svg
- type: file
path: ../src-tauri/icons/32x32.png
- type: file
path: ../src-tauri/icons/128x128.png
- type: file
path: ../src-tauri/icons/128x128@2x.png
- type: file
path: ../src-tauri/icons/icon.png
- type: file
path: libs/libayatana-appindicator3.so.1
- type: file
path: libs/libayatana-appindicator3.so.1.0.0
- type: file
path: libs/libayatana-ido3-0.4.so.0
- type: file
path: libs/libayatana-ido3-0.4.so.0.0.0
- type: file
path: libs/libayatana-indicator3.so.7
- type: file
path: libs/libayatana-indicator3.so.7.0.0
- type: file
path: libs/libdbusmenu-glib.so.4
- type: file
path: libs/libdbusmenu-glib.so.4.0.12
- type: file
path: libs/libdbusmenu-gtk3.so.4
- type: file
path: libs/libdbusmenu-gtk3.so.4.0.12
build-commands:
- |
set -euo pipefail
mkdir -p deb-extract
cd deb-extract
ar x "../drop-app.deb"
tar xzf data.tar.gz
cd ..
# Binary
install -Dm755 deb-extract/usr/bin/drop-app /app/bin/drop-app
# Desktop file (use Flatpak-specific version with correct paths)
install -Dm644 org.droposs.client.desktop /app/share/applications/org.droposs.client.desktop
# AppStream metainfo
install -Dm644 org.droposs.client.metainfo.xml /app/share/metainfo/org.droposs.client.metainfo.xml
# System tray indicator library
install -Dm755 libayatana-appindicator3.so.1.0.0 /app/lib/libayatana-appindicator3.so.1.0.0
ln -sf libayatana-appindicator3.so.1.0.0 /app/lib/libayatana-appindicator3.so.1
install -Dm755 libayatana-ido3-0.4.so.0.0.0 /app/lib/libayatana-ido3-0.4.so.0.0.0
ln -sf libayatana-ido3-0.4.so.0.0.0 /app/lib/libayatana-ido3-0.4.so.0
install -Dm755 libayatana-indicator3.so.7.0.0 /app/lib/libayatana-indicator3.so.7.0.0
ln -sf libayatana-indicator3.so.7.0.0 /app/lib/libayatana-indicator3.so.7
install -Dm755 libdbusmenu-glib.so.4.0.12 /app/lib/libdbusmenu-glib.so.4.0.12
ln -sf libdbusmenu-glib.so.4.0.12 /app/lib/libdbusmenu-glib.so.4
install -Dm755 libdbusmenu-gtk3.so.4.0.12 /app/lib/libdbusmenu-gtk3.so.4.0.12
ln -sf libdbusmenu-gtk3.so.4.0.12 /app/lib/libdbusmenu-gtk3.so.4
# SVG icon (scalable)
install -Dm644 org.droposs.client.svg /app/share/icons/hicolor/scalable/apps/org.droposs.client.svg
# PNG icons at standard hicolor sizes (install from source PNGs, renaming to app ID)
install -Dm644 32x32.png /app/share/icons/hicolor/32x32/apps/org.droposs.client.png
install -Dm644 128x128.png /app/share/icons/hicolor/128x128/apps/org.droposs.client.png
install -Dm644 128x128@2x.png /app/share/icons/hicolor/256x256/apps/org.droposs.client.png
install -Dm644 icon.png /app/share/icons/hicolor/512x512/apps/org.droposs.client.png
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# prepare-deb.sh — Bridge between versioned Tauri .deb output and the
# predictable drop-app.deb path expected by the Flatpak manifest.
#
# Usage:
# ./flatpak/prepare-deb.sh
#
# Run this after `pnpm tauri build --bundles deb` to create a symlink
# at flatpak/drop-app.deb pointing to the actual versioned .deb file
# in the Tauri build output directory.
#
# CI note: In GitHub Actions, the Tauri build step produces the .deb
# into src-tauri/target/release/bundle/deb/. This script is called before
# flatpak-builder so the manifest's predictable path resolves correctly.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEB_DIR="${SCRIPT_DIR}/../src-tauri/target/release/bundle/deb"
if [ ! -d "$DEB_DIR" ]; then
echo "Error: Tauri build output directory not found: $DEB_DIR" >&2
echo "Make sure to run 'pnpm tauri build --bundles deb' first." >&2
exit 1
fi
# Find the actual .deb — use glob to handle version changes
shopt -s nullglob
debs=("$DEB_DIR"/*_amd64.deb)
shopt -u nullglob
if [ ${#debs[@]} -eq 0 ]; then
echo "Error: No .deb file found in $DEB_DIR" >&2
echo "Expected a file matching *_amd64.deb" >&2
exit 1
fi
# Use the first match (there should only be one)
actual_deb="${debs[0]}"
link_target="$SCRIPT_DIR/drop-app.deb"
ln -sf "$(realpath "$actual_deb")" "$link_target"
echo "Created symlink: $link_target -> $(realpath "$actual_deb")"
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# prepare-libs.sh — Copy host system tray libraries into flatpak/libs/
# for bundling in the Flatpak build.
#
# These libraries (libayatana-appindicator3, libdbusmenu) are not available
# in the GNOME Platform runtime, so we bundle them from the host.
#
# Usage:
# ./flatpak/prepare-libs.sh
#
# In CI (Ubuntu), run this after:
# sudo apt-get install -y libayatana-appindicator3-dev libdbusmenu-gtk3-dev
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIBS_DIR="${SCRIPT_DIR}/libs"
mkdir -p "$LIBS_DIR"
# Library files needed at runtime — copied from host system
declare -A REQUIRED_LIBS=(
["libayatana-appindicator3.so.1"]="libayatana-appindicator3.so.1"
["libayatana-appindicator3.so.1.0.0"]="libayatana-appindicator3.so.1.0.0"
["libayatana-ido3-0.4.so.0"]="libayatana-ido3-0.4.so.0"
["libayatana-ido3-0.4.so.0.0.0"]="libayatana-ido3-0.4.so.0.0.0"
["libayatana-indicator3.so.7"]="libayatana-indicator3.so.7"
["libayatana-indicator3.so.7.0.0"]="libayatana-indicator3.so.7.0.0"
["libdbusmenu-glib.so.4"]="libdbusmenu-glib.so.4"
["libdbusmenu-glib.so.4.0.12"]="libdbusmenu-glib.so.4.0.12"
["libdbusmenu-gtk3.so.4"]="libdbusmenu-gtk3.so.4"
["libdbusmenu-gtk3.so.4.0.12"]="libdbusmenu-gtk3.so.4.0.12"
)
all_found=true
for soname in "${!REQUIRED_LIBS[@]}"; do
libfile="${REQUIRED_LIBS[$soname]}"
# Search common library paths
found=$(find /usr/lib64 /usr/lib /lib64 /lib -name "$libfile" -type f 2>/dev/null | head -1)
if [ -n "$found" ]; then
cp -f "$found" "$LIBS_DIR/$soname"
echo "$soname ($(du -h "$found" | cut -f1))"
else
# Try with apt-file or ldconfig
found_ld=$(ldconfig -p 2>/dev/null | grep "$libfile" | awk '{print $NF}' | head -1)
if [ -n "$found_ld" ] && [ -f "$found_ld" ]; then
cp -f "$found_ld" "$LIBS_DIR/$soname"
echo "$soname via ldconfig ($(du -h "$found_ld" | cut -f1))"
else
echo "$soname NOT FOUND — install libayatana-appindicator3-dev and libdbusmenu-gtk3-dev" >&2
all_found=false
fi
fi
done
if [ "$all_found" = false ]; then
echo "Error: Some required libraries were not found." >&2
echo "Install them and re-run:" >&2
echo " # Fedora: sudo dnf install libayatana-appindicator-gtk3-devel libdbusmenu-gtk3-devel" >&2
echo " # Ubuntu: sudo apt-get install libayatana-appindicator3-dev libdbusmenu-gtk3-dev" >&2
exit 1
fi
echo "All system tray libraries prepared in $LIBS_DIR"