Skip to content

ci(flatpak): spike how far Flathub actually is - #347

Merged
EtienneLescot merged 2 commits into
mainfrom
claude/flathub-spike
Aug 11, 2026
Merged

ci(flatpak): spike how far Flathub actually is#347
EtienneLescot merged 2 commits into
mainfrom
claude/flathub-spike

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Refs #335.

Why a spike and not a manifest

Flathub forbids the route every other channel in this repository takes:

All source available submissions must be built entirely from source code. This requirement applies to the main application component defined in the manifest, as well as any runtime dependencies included in the manifest.

OpenScreen is MIT, so it is source-available, so repackaging the .deb is out. extra-data is out on the same reading — it exists for non-redistributable sources, and a submission from the upstream author carries the redistribution right implicitly, so there are no grounds to use it.

That turns the last open item on #335 from "write a manifest" into a port, and the expensive unknown is ffmpeg:

  • scripts/fetch-ffmpeg.mjs pins a BtbN prebuilt shared tree, rejected by the same rule.
  • That tree is not merely convenient. scripts/build-linux-compositor-addon.mjs renames every ffmpeg dynamic symbol in the libraries it ships, because Electron links Chromium's own stripped libffmpeg.so into the same address space and the addon would otherwise bind to it. It needs headers and libraries it owns.
  • Which rules out org.freedesktop.Platform.ffmpeg-full as a substitute: libraries, no dev headers.

So ffmpeg has to be built from source inside the manifest, and whether that reproduces the tree the addon was tested against is exactly what nobody knows yet.

What this PR is

A dispatch-only workflow that builds a deliberately provisional manifest and reports where it dies. Every stage is continue-on-error, because a spike that stops at the first failure reports one fact and this one reports six:

Stage Question
A Do runtime 24.08, the Electron2 BaseApp and node22 even exist at these versions?
B Do they install together?
C Can flatpak-node-generator digest this package-lock.json?
D Do both Cargo.lock files vendor?
E What does upstream ffmpeg actually publish, given the pin is a snapshot (n8.1.2-34-g9b6c8969e0) and not a release?
F Where does flatpak-builder stop?

The verdict lands in the run summary, not the log. Stage F failing is the expected result, not the finding — A–E are what decide whether this port is a week or a quarter.

The manifest's ffmpeg sha256 is deliberately all zeroes: the pin is a snapshot, so there is no honest digest to write, and stage E resolves what upstream really ships. A guessed digest must never reach a submission.

Notes

  • Costs nothing until dispatched: workflow_dispatch only, no event trigger.
  • It has to sit on main to be dispatchable at all — that is the only reason this is a PR rather than a branch.
  • Delete both files once the questions are answered. This is scaffolding, not a feature.
  • Verified locally: both YAML files parse, all 8 shell blocks pass bash -n.

Summary by CodeRabbit

  • New Features

    • Added experimental Flatpak packaging for OpenScreen.
    • Supports desktop distribution with Electron, Node.js, Rust, and FFmpeg components.
    • Includes sandbox permissions and packaging settings.
  • Chores

    • Added a manually triggered Flatpak readiness workflow.
    • Validates runtime components, dependency sources, and application builds.
    • Uploads build artifacts and reports results for each validation stage.

Flathub forbids the route every other channel here takes: "All source
available submissions must be built entirely from source code. This
requirement applies to the main application component defined in the
manifest, as well as any runtime dependencies included in the manifest."
OpenScreen is MIT, so repackaging the .deb is out, and extra-data is out too
— it exists for non-redistributable sources, and upstream authorship implies
the redistribution right.

That turns the remaining #335 item from "write a manifest" into a port, and
the expensive unknown is ffmpeg. scripts/fetch-ffmpeg.mjs pins a BtbN
*prebuilt* shared tree, which Flathub rejects on the same rule, and the tree
is not merely convenient: build-linux-compositor-addon.mjs renames every
ffmpeg dynamic symbol in the libraries it ships, because Electron links
Chromium's own stripped libffmpeg.so into the same process and the addon
would otherwise bind to that. It needs headers and libraries it owns, so the
runtime's ffmpeg-full extension — libraries, no headers — cannot stand in
either.

So this is a spike, not a submission. The manifest is built by a
dispatch-only workflow whose stages all continue on error, because a spike
that stops at the first failure reports one fact and this one reports six:
whether the guessed runtime, base and node extension versions exist at all,
whether flatpak-node-generator can digest this lockfile, whether both
Cargo.locks vendor, and where the build actually dies. The verdict lands in
the run summary rather than the log.

The ffmpeg sha256 in the manifest is deliberately all zeroes. The pinned
build is a BtbN snapshot (n8.1.2-34-g9b6c8969e0), not an upstream release, so
there is no honest digest to write yet — stage E resolves what upstream
actually publishes and the mismatch is itself a finding.
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds an OpenScreen Flatpak manifest and a manually dispatched GitHub Actions workflow. The workflow checks Flathub components, prepares offline dependencies, resolves FFmpeg, runs flatpak-builder, uploads artifacts, and records a final verdict.

Changes

Flatpak packaging

Layer / File(s) Summary
Flatpak platform and FFmpeg configuration
build/flatpak/com.getopenscreen.OpenScreen.yml
Defines the Freedesktop runtime, Electron BaseApp, Node and Rust extensions, sandbox permissions, build environment, and FFmpeg source module.
OpenScreen application module
build/flatpak/com.getopenscreen.OpenScreen.yml
Retrieves OpenScreen v1.9.2, includes generated dependency sources, builds the application offline, and stages it under /app/openscreen.
Workflow setup and readiness checks
.github/workflows/flatpak-spike.yml
Adds manual dispatch, Flatpak tooling setup, Flathub availability checks, and component installation.
Dependency preparation and Flatpak build
.github/workflows/flatpak-spike.yml
Generates npm sources, vendors Rust dependencies, computes the FFmpeg digest, and runs flatpak-builder.
Artifacts and build verdict
.github/workflows/flatpak-spike.yml
Uploads the build log and generated sources, then records stage results and metadata in the workflow summary.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant GitHubActions as GitHub Actions
  participant Flathub
  participant DependencyTools as flatpak-builder-tools
  participant FlatpakBuilder as flatpak-builder
  participant Artifacts as Artifact storage

  Developer->>GitHubActions: Manually dispatch Flatpak spike
  GitHubActions->>Flathub: Check runtime, SDK, BaseApp, Node, and Rust branches
  GitHubActions->>Flathub: Install required Flatpak components
  GitHubActions->>DependencyTools: Generate offline npm sources
  GitHubActions->>GitHubActions: Vendor Rust dependencies and resolve FFmpeg digest
  GitHubActions->>FlatpakBuilder: Build OpenScreen from manifest
  GitHubActions->>Artifacts: Upload build log and generated npm sources
  GitHubActions->>Developer: Write stage verdict and metadata
Loading

Possibly related issues

  • Issue 341 — The manifest and workflow implement the from-source Flatpak packaging plan and workflow validation.
  • Issue 335 — The changes add the missing Flatpak manifest and build readiness workflow.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the CI Flatpak spike and its investigation of Flathub readiness.
Description check ✅ Passed The description explains the purpose, issue reference, implementation, temporary scope, workflow stages, and local validation, but omits several template headings and classifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/flathub-spike

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/flatpak-spike.yml:
- Around line 66-72: Update the remote query handling around FOUND in the Stage
A workflow: capture the exit status of flatpak remote-ls without allowing ||
true to hide failures, report “QUERY FAILED” to GITHUB_STEP_SUMMARY when the
query fails, and make Stage A fail. Preserve the existing branches and “NOT
PUBLISHED” reporting only for successful queries.
- Around line 97-99: Update the Stage C source-generation step to clone the
maintained flatpak-builder-tools repository from
https://github.com/flatpak/flatpak-builder-tools.git at a pinned commit, install
its node project, and invoke the documented flatpak-node-generator entry point
instead of the nonexistent /tmp/fbt/node script.

In `@build/flatpak/com.getopenscreen.OpenScreen.yml`:
- Around line 112-114: Update the Flatpak manifest sources and build setup to
include both vendored Cargo trees and their generated Cargo configuration files,
then install the matching source-replacement configuration into Cargo’s expected
location before the native build commands. Ensure npm run build:native:linux and
npm run build:native:compositor:linux use these vendored sources without network
access.
- Around line 112-120: Update the Flatpak build commands after electron-builder
runs: copy the bundle from release/1.9.2/linux-unpacked into /app/main, install
/app/bin/openscreen to invoke the bundled executable through zypak-wrapper.sh,
and provide Electron’s binary in electron_config_cache so npm ci --offline can
complete without downloading it.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 71763018-f95e-4ff6-b5db-9e0200489c23

📥 Commits

Reviewing files that changed from the base of the PR and between d09c0c2 and ec6c4ff.

📒 Files selected for processing (2)
  • .github/workflows/flatpak-spike.yml
  • build/flatpak/com.getopenscreen.OpenScreen.yml

Comment thread .github/workflows/flatpak-spike.yml Outdated
Comment thread .github/workflows/flatpak-spike.yml Outdated
Comment thread build/flatpak/com.getopenscreen.OpenScreen.yml
Comment thread build/flatpak/com.getopenscreen.OpenScreen.yml
Stage C cloned `flathub-infra/flatpak-builder-tools`, which is a 404, and then
invoked `node/flatpak-node-generator.py`, which is not how that tool ships —
`node/` is a poetry project exposing a `flatpak-node-generator` console script.
Two mistakes stacked on the one stage the spike exists for: whether this
lockfile can be turned into offline sources at all. It would have failed for
reasons that say nothing about the port. Cloned from `flatpak/`, pinned to
737c008, installed into a venv, so the answer is about the lockfile and stays
comparable between runs.

Stage A queried the remote once per id behind `|| true`, so a broken remote
reported six lines of "NOT PUBLISHED" — the single wrong answer that stage can
give, since it reads as "Flathub does not ship the runtime" when it means the
question never got asked. One query, status kept, and an explicit QUERY FAILED
that draws no conclusion.

Both found by CodeRabbit on #347.
@EtienneLescot
EtienneLescot merged commit 0d5f24b into main Aug 11, 2026
18 of 21 checks passed
@EtienneLescot
EtienneLescot deleted the claude/flathub-spike branch August 11, 2026 21:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/flatpak-spike.yml (1)

157-157: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Report download failures separately from a missing release.

At .github/workflows/flatpak-spike.yml:152-157, curl -fsSL uses the same branch for HTTP errors and transport or TLS failures. Capture the HTTP status and curl exit status separately. Emit the snapshot warning only for HTTP 404 or 410. Report other failures as download failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/flatpak-spike.yml at line 157, Update the URL validation
logic around the curl invocation in the workflow to capture HTTP status and curl
exit status independently. Emit the existing snapshot warning only when the HTTP
status is 404 or 410; report transport, TLS, and other curl failures as download
failures, while preserving successful-download handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/flatpak-spike.yml:
- Line 62: Update the Stage A catalog query and filtering around CATALOG to
include application, branch, and architecture columns, and only mark entries
available when the branch is 24.08 and architecture is x86_64. Preserve the
existing application availability handling for matching entries.

---

Outside diff comments:
In @.github/workflows/flatpak-spike.yml:
- Line 157: Update the URL validation logic around the curl invocation in the
workflow to capture HTTP status and curl exit status independently. Emit the
existing snapshot warning only when the HTTP status is 404 or 410; report
transport, TLS, and other curl failures as download failures, while preserving
successful-download handling.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d44743c-49e4-49cb-b504-8e6b90d82b38

📥 Commits

Reviewing files that changed from the base of the PR and between ec6c4ff and b56227c.

📒 Files selected for processing (1)
  • .github/workflows/flatpak-spike.yml

# turn a broken remote into six "NOT PUBLISHED" lines, which is the one
# wrong answer this stage must never give: it would read as "Flathub does
# not ship the runtime" when it means "the question never got asked".
if ! CATALOG=$(flatpak remote-ls flathub --user --columns=application,branch 2>&1); then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

catalog="$(flatpak remote-ls flathub --user \
  --columns=application,branch,arch)"

for id in \
  org.freedesktop.Platform \
  org.freedesktop.Sdk \
  org.electronjs.Electron2.BaseApp \
  org.freedesktop.Sdk.Extension.node22 \
  org.freedesktop.Sdk.Extension.node20 \
  org.freedesktop.Sdk.Extension.rust-stable
do
  awk -v id="$id" \
    '$1 == id && $2 == "24.08" && $3 == "x86_64" { found = 1 }
     END { exit !found }' <<<"$catalog"
done

Repository: getopenscreen/openscreen

Length of output: 207


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

file=".github/workflows/flatpak-spike.yml"
printf '%s\n' '--- relevant workflow lines ---'
sed -n '1,120p' "$file"

printf '%s\n' '--- related references ---'
rg -n -C 3 'remote-ls|flatpak install|24\.08|x86_64|branch|arch|FOUND|CATALOG' "$file"

Repository: getopenscreen/openscreen

Length of output: 8156


🌐 Web query:

Flatpak command reference remote-ls --columns application branch arch output

💡 Result:

The --columns option for the flatpak remote-ls command allows you to specify which information fields are displayed for each reference found in a remote repository [1][2]. Usage: --columns=FIELD,... You can provide a comma-separated list of fields or use the --columns option multiple times to add specific fields to the output [2]. Available fields include: name: Shows the application or runtime name [2]. description: Shows the description of the application [2]. application: Shows the application or runtime ID [2]. arch: Shows the architecture [2]. branch: Shows the branch [2]. version: Shows the version [2]. ref: Shows the full reference string [2]. origin: Shows the origin remote [2]. commit: Shows the active commit [2]. runtime: Shows the runtime used by the application [2]. installed-size: Shows the installed size [2]. download-size: Shows the download size [2]. options: Shows options associated with the ref [2]. all: Displays all available columns [1][2]. help: Lists all valid field names [2]. Additional notes: - You can abbreviate field names to their unique prefix (e.g., --columns=app for application) [1][2]. - You can change the ellipsization behavior for a column by appending:s (start),:m (middle),:e (end), or:f (full) to the field name (e.g., --columns=application:f) [2]. - For example, running flatpak remote-ls --columns=application,branch,arch will output only the application ID, branch, and architecture for the refs found [2].

Citations:


Restrict Stage A to the Stage B ref.

Stage B installs x86_64/24.08, but Stage A accepts any branch and architecture. Query application,branch,arch and require $2 == "24.08" and $3 == "x86_64" before marking the application as available.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/flatpak-spike.yml at line 62, Update the Stage A catalog
query and filtering around CATALOG to include application, branch, and
architecture columns, and only mark entries available when the branch is 24.08
and architecture is x86_64. Preserve the existing application availability
handling for matching entries.

EtienneLescot added a commit that referenced this pull request Aug 11, 2026
Stage C cloned `flathub-infra/flatpak-builder-tools`, which is a 404, and then
invoked `node/flatpak-node-generator.py`, which is not how that tool ships —
`node/` is a poetry project exposing a `flatpak-node-generator` console script.
Two mistakes stacked on the one stage the spike exists for: whether this
lockfile can be turned into offline sources at all. It would have failed for
reasons that say nothing about the port. Cloned from `flatpak/`, pinned to
737c008, installed into a venv, so the answer is about the lockfile and stays
comparable between runs.

Stage A queried the remote once per id behind `|| true`, so a broken remote
reported six lines of "NOT PUBLISHED" — the single wrong answer that stage can
give, since it reads as "Flathub does not ship the runtime" when it means the
question never got asked. One query, status kept, and an explicit QUERY FAILED
that draws no conclusion.

Both found by CodeRabbit on #347.
EtienneLescot added a commit that referenced this pull request Aug 12, 2026
Run 31575509206 got ffmpeg compiled and then died in `npm ci --offline` with
ENOTCACHED on zustand. Not an npm problem: the cache paths were invented. The
upstream Electron manifest documents them, and they are positional —
flatpak-node-generator lays its output out under `flatpak-node/` in the module
build dir, so both variables have to name that exact path:

  XDG_CACHE_HOME:   /run/build/openscreen/flatpak-node/cache
  npm_config_cache: /run/build/openscreen/flatpak-node/npm-cache

XDG_CACHE_HOME looked like a nicety and is not: it is where the Electron
binary download is cached, so without it electron's postinstall reaches for
the network that the sandbox does not have.

Cargo gets the same treatment — CARGO_HOME must be the path
flatpak-cargo-generator hardcodes, since it writes its replace-with config
there and vendors into $CARGO_HOME/vendor. Stage D now emits real source
lists for both lockfiles instead of only proving they resolve, and it counts
the destinations the two share, because the generator has no way to separate
their vendor directories and crates common to both trees land on the same
path twice. Better reported than discovered inside flatpak-builder.

Electron needs a zypak wrapper or it fails on the SUID sandbox helper, so
there is a launcher script and `command:` finally resolves to something. Also
here: a desktop file named after the app id, icons renamed on the way in
because Flatpak only exports app-id-named ones, and the metainfo launchable
rewritten at install time — that file's own comment already said a Flatpak
build would have to, and now something does. Rewritten rather than edited, so
it stays correct for the deb, and the rewrite fails loudly if it ever stops
matching.

The git source is a `type: dir` for now. It cannot be the tag Flathub wants:
v1.9.2 predates both the metainfo and the desktop file, so a tagged build
would fail on files that do not exist in it.

Stage E is deleted. It resolved a digest that is now pinned in the manifest,
and its only other act was to report "does not exist" for a connection reset
— the same conflation of a broken query with a negative answer that the
review of #347 corrected in Stage A.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant