flutpak 0.7.1
flutpak: ^0.7.1 copied to clipboard
CLI tool that generates Flatpak manifests and offline source bundles for Flutter applications. One command prepares everything needed for a Flathub-compatible offline build: generated-sources.json, ma [...]
flutpak #
Dart CLI tool that automates Flatpak packaging for Flutter applications.
Describe your config once in flutpak.yaml, run flutpak init once to
scaffold your template manifest, then run flutpak generate on every CI
build to produce a fully-substituted, flatpak-builder-ready output.
Highlights #
init+generatesplit — one-time scaffold vs. every-build generation; the editable template is committed to git, the substituted output is gitignored- No Python, no local Flutter SDK — pure Dart, compiles to a single native binary for CI; engine versions fetched from GitHub API via
flutter.ref - Standalone
flutpak.yaml— recommended; keeps Flatpak config separate from your Dart project metadata (aflutpak:section inpubspec.yamlis also supported) - yaml_edit injection —
generatesetstag:andcommit:directly in the git source block viayaml_edit; no placeholder strings to maintain in the template - Foreign deps registry — known packages (e.g.
objectbox_flutter_libs,sqlite3_flutter_libs) resolved and injected automatically; local overrides viaforeign-deps:in config - Validation on every run —
generateerrors early if the template is missing or itsapp-id,command, orruntime-versiondiverge from config - Retry on transient errors — pub.dev and Flutter artifact downloads retry on 429/5xx
sdk-modcommand — generates a standalone Flutter SDK module JSON for!includein any manifest or SDK Extension
flutpak vs flatpak-flutter #
Both tools solve the same core problem: Flutter apps cannot build offline inside the Flatpak sandbox, so all dependencies must be pre-downloaded and listed as flatpak-builder sources before the build starts. flatpak-flutter (Python) and flutpak (Dart) take different approaches to the same goal.
What they share #
- Same problem domain — both produce a
generated-sources.jsonand an offline-ready Flatpak manifest for Flutter apps. - Compatible
foreign_deps.jsonformat — the registry schema is identical; entries fromflatpak-flutter's registry can be used in flutpak as-is. flutpak extends the schema with one field (crlf:on patch sources);flatpak-flutterignores unknown fields. - Same flatpak-builder output format — both write standard
flatpak-builder
sourcesJSON arrays. The generated manifests are consumed byflatpak-builderin exactly the same way. - Same Flathub requirements — manifests from either tool satisfy Flathub's "build from source / no network during build" requirements.
How they differ #
| flutpak | flatpak-flutter | |
|---|---|---|
| Language / distribution | Dart, compiled to a single native binary | Python, installed via pip or Docker |
| Usage model | App authors packaging their own app | External maintainers / Flathub infra packaging many apps |
| Project access | Works inside your own repo — no clone needed | Clones the app repo and runs flutter pub get locally |
| Workflow | init once → generate on every CI build |
Single command regenerates everything each time |
| Config | Structured flutpak.yaml (or pubspec.yaml section) |
CLI flags + the manifest itself |
| Template lifecycle | Template committed to git; generated output gitignored | Input manifest modified in place; output replaces it |
Flutter SDK needed for generate? |
No — engine versions fetched from GitHub API via flutter.ref |
Yes — Flutter SDK must be installed and discoverable |
| Foreign dep Cargo.lock discovery | Planned (downloads pub archives automatically) | Reads from locally cloned source after flutter pub get |
| Rust / Cargo support | Planned (see backlog) | Yes |
| Version matching in registry | Highest registry version ≤ installed | Same |
Which one to use #
Use flutpak if you are the app author and want a config-driven, CI-native
workflow where the template manifest lives in your repo alongside your source
code. flutpak's generate step runs without a local Flutter SDK installation
and produces a gitignored generated/ directory that CI rebuilds on every push.
Use flatpak-flutter if you are a Flathub maintainer packaging an app you do not own, or if you need Rust/Cargo support today. Its all-in-one approach (clone → pub get → generate → output) is better suited for batch packaging workflows and for apps with complex native dependencies.
Both tools are compatible at the registry level — if a package is supported in
flatpak-flutter's foreign_deps.json it can be added to flutpak's registry
with minimal or no changes.
Demo application #
The repository ships a real Flutter application in examples/demo_app/
that is built on every pull request via the demo.yml workflow — an end-to-end
proof of concept that the tool works, and a proof of work that every PR
is gated on a real Flatpak build.
The CI pipeline runs the full cycle:
flutpak generate --commit <sha>— generates the manifest and sourcesflatpak-builderviaflathub-build— builds and lints the Flatpakflatpak install+ smoke test — installs the app and verifies it starts
The demo is intentionally built from the same git repository it lives in
(repo-url: https://github.com/o-murphy/flutpak.git), with
subdir: examples/demo_app in the Flatpak module. This exercises several
features added in v0.6.0:
- Subdirectory project support — the Flutter project lives inside a larger
git repo; the generated
cpstamp commands use$FLATPAK_BUILDER_BUILDDIRso they resolve correctly regardless ofsubdir:. - LLVM SDK extension auto-injection —
runtime-version: '25.08'is all that is needed;flutpakinjectsorg.freedesktop.Sdk.Extension.llvm20automatically. --configwith a subdirectory path — the workflow passes--config examples/demo_app/flutpak.yaml; all paths are resolved relative to the config file's directory.- Minimum viable
flutpak.yaml— the demo config has onlyapp-id,runtime-version,command,flutter.ref, andrepo-url; everything else is auto-detected.
The demo app config: examples/demo_app/flutpak.yaml
The template manifest: examples/demo_app/flatpak/io.github.o_murphy.flutpak.demo.yml
Table of Contents #
- flutpak
- Highlights
- flutpak vs flatpak-flutter
- Demo application
- Table of Contents
- Prerequisites
- Installation
- Step-by-step setup
- Foreign deps registry
- Full config reference
- Commands reference
- Manifest lifecycle
- CI/CD integration
- Validating and building locally
- How it works
- Building from source / contributing
- License
Prerequisites #
| Tool | When needed | Notes |
|---|---|---|
| Linux | Always | flutpak targets Linux Flatpak packaging only |
| git | Always | Version detection and commit-hash resolution |
| Internet access | init, generate |
Downloads from pub.dev, storage.googleapis.com, and raw.githubusercontent.com |
| Flutter SDK | CI — running the app | flutter pub get and flutter build still need a real SDK in CI; flutpak generate itself does not — engine versions are fetched via flutter.ref |
| Dart SDK ≥ 3.0 | Building from source only | Not required when using a pre-built binary |
flatpak-builder |
Local Flatpak builds | Only needed when actually building the Flatpak, not during flutpak generate |
org.freedesktop.Sdk |
Local Flatpak builds | Install via flatpak install flathub org.freedesktop.Sdk//25.08 |
Installation #
Pre-built binary (recommended) #
Download the binary for your platform from the
releases page and place it
somewhere on your $PATH:
sudo mv flutpak /usr/local/bin/
Via setup-flutpak action (CI) #
The repository ships a composite action that compiles and installs flutpak
automatically. See CI/CD integration for full usage.
- uses: o-murphy/flutpak/.github/actions/setup-flutpak@v0.6.1
From source #
Using make:
git clone https://github.com/o-murphy/flutpak.git
cd flutpak
make build # regenerates version.dart + compiles binary
sudo mv flutpak /usr/local/bin/
Without make:
dart compile exe bin/flutpak.dart -o flutpak --define=version=$(git describe --tags --always)
Step-by-step setup #
Step 1 — Create flutpak.yaml #
Create a standalone flutpak.yaml in your project root. This is the
recommended approach — it keeps Flatpak config separate from your Dart project
metadata and avoids any key-convention ambiguity with pubspec.yaml.
Tip
Alternative: you can put a flutpak: section directly in pubspec.yaml
instead, but not both at once. Note that pubspec.yaml conventionally uses
snake_case for its own keys; flutpak uses kebab-case, so a standalone
flutpak.yaml is cleaner.
Minimum viable config — only app-id is truly required:
# flutpak.yaml
app-id: io.github.YourOrg.YourApp
Everything else is either optional or auto-detected at runtime. For Flutter projects, add the Flutter git ref (tag, branch, or commit SHA):
# flutpak.yaml
app-id: io.github.YourOrg.YourApp
flutter:
ref: "3.44.1" # tag, "stable", "main", or commit SHA
flutter.ref tells flutpak generate which Flutter version to fetch engine
artifacts for — no local Flutter installation required for source generation.
The Flutter SDK is still needed in CI to run flutter pub get / flutter build.
Tip
Sandbox permissions (finish-args) — flutpak init writes sensible
Flutter desktop defaults into the template. To add extras (e.g.
--filesystem=xdg-documents), list them under finish-args: in your config
— they are merged after the mandatory args with duplicates removed. You can
also edit the template directly.
See Full config reference for all options.
Step 2 — Create required asset files #
flutpak init validates that these files exist before generating anything.
Create and commit them first:
<your-project>/
├── pubspec.yaml
├── pubspec.lock
└── app/
└── share/
├── metainfo/
│ └── <app-id>.metainfo.xml
├── applications/
│ └── <app-id>.desktop
└── icons/
└── hicolor/
└── 256x256/
└── apps/
└── <app-id>.png # minimum 256x256 required by Flathub
Minimal <app-id>.desktop:
[Desktop Entry]
Name=Your App
Exec=yourapp
Icon=io.github.YourOrg.YourApp
Type=Application
Categories=Utility;
Minimal <app-id>.metainfo.xml:
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>io.github.YourOrg.YourApp</id>
<metadata_license>MIT</metadata_license>
<project_license>MIT</project_license>
<name>Your App</name>
<summary>Short one-line description</summary>
<releases>
<release version="0.1.0" date="2025-01-01"/>
</releases>
</component>
For full requirements on these files, see the Flathub app-author documentation.
Step 3 — First run #
flutpak init
This is a one-time command. It generates the following files and then
immediately runs generate to populate flatpak/generated/:
flatpak/
├── <app-id>.yml # editable template manifest — commit this
├── <name>-wrapper.sh # Flutter launcher wrapper — commit this
├── .gitignore # contains: generated/
└── generated/ # gitignored, ready for flatpak-builder
├── <app-id>.yml
├── generated-sources.json
└── patches/
Commit flatpak/<app-id>.yml, flatpak/<name>-wrapper.sh,
and flatpak/.gitignore. Do not commit
flatpak/generated/ — it is gitignored by design.
flutpak init errors if the template already exists. Use --force to
overwrite an existing scaffold.
Step 4 — Review and customize the template #
Open flatpak/<app-id>.yml and adjust build-commands, sdk-extensions,
finish-args, and any other fields for your project. The template is yours —
flutpak never overwrites it on subsequent runs.
The generated template already contains inline guidance comments that explain which sections are safe to edit. Key rules:
| Section | Guidance |
|---|---|
finish-args: |
Safe to edit — add/remove sandbox permissions |
build-commands: |
Safe to edit — add steps, adjust install paths |
build-options: |
Safe to edit — add env vars, path overrides |
sdk-extensions: |
Safe to edit |
modules: |
Safe to edit — add extra modules |
sources: — extra archives |
Safe to add/remove |
tag: / commit: in git source |
Set automatically by generate via yaml_edit — you can leave them absent or set them manually; generate will overwrite them |
Flutter cp stamp commands |
Use $FLATPAK_BUILDER_BUILDDIR/flutter/… — always the module build root, works with or without subdir: |
Patch sources (type: patch) |
Do not add manually — injected automatically by generate from the foreign deps registry and foreign-deps: config; adding them to the template causes duplicates |
Tip
If your Flutter project lives in a subdirectory of the git repo (e.g. apps/myapp/),
set subdir: apps/myapp in flutpak.yaml — flutpak init will emit it on the
app module automatically. The generated cp stamp commands use $FLATPAK_BUILDER_BUILDDIR
(always the module root, not the subdir), so they resolve correctly. Other paths in
build-commands that reference files inside the Flutter project remain relative to subdir:.
Step 5 — Build locally #
Point flatpak-builder at the generated manifest:
flatpak-builder --repo=repo --force-clean build-dir \
flatpak/generated/<app-id>.yml
See Validating and building locally for the full lint and validation workflow.
Step 6 — CI: generate on each release #
On every release build, run generate with the release tag:
flutpak generate --tag ${{ github.ref_name }}
This reads the committed template, resolves the tag and commit SHA, sets
tag: and commit: in the git source block, generates generated-sources.json,
and writes everything to flatpak/generated/.
See CI/CD integration for a complete GitHub Actions workflow.
Step 7 — Submit to Flathub #
Open a PR to your app's Flathub repository. Copy the contents of
flatpak/generated/ into the repo root:
<flathub-repo>/
├── <app-id>.yml # flatpak/generated/<app-id>.yml
├── generated-sources.json # flatpak/generated/generated-sources.json
└── patches/ # flatpak/generated/patches/ (if any)
For complete submission requirements see the Flathub app-author documentation.
Foreign deps registry #
flutpak generate automatically resolves known pub packages from a remote
registry at
foreign_deps/foreign_deps.json, using
the same format as flatpak-flutter. For each package found in your lock
files the registry provides the complete set of flatpak sources — prebuilt
archives and patches — needed for an offline Flatpak build.
How it works #
- The registry is fetched from GitHub on every
generaterun (network required; cached in~/.cache/flutpak/as offline fallback). - For every package in your
pubspec.lockthat appears in the registry at the exact locked version, its sources are appended togenerated-sources.json. - Patch files from the registry are downloaded to
generated/patches/<path>and referenced with a relativepath:so flatpak-builder can find them alongside the manifest.
Local overrides and suppression #
Local foreign-deps: entries in flutpak.yaml are deep-merged on top of the
remote registry before resolution. Two formats are supported:
foreign-deps:
# Shorthand — version resolved from pubspec.lock:
sqlite3_flutter_libs:
manifest:
sources:
- type: patch
path: patches/sqlite3.patch
crlf: true # flutpak-specific, stripped from output
# Versioned — explicit version key:
some_package:
"1.2.3":
manifest:
sources: [] # empty list = suppress remote entry entirely
- Local entries override the registry for the same
(package, version). sources: []suppresses a remote registry entry without replacing it.crlf: trueon atype: patchsource normalises the file to CRLF after download; the key is stripped before writing togenerated-sources.json.
Currently supported packages #
| Package | Versions |
|---|---|
audiotags |
1.4.5 |
flutter_new_pipe_extractor |
0.1.0 |
flutter_webrtc |
1.3.0 |
fvp |
0.35.0 |
media_kit_libs_linux |
1.2.1 |
pdfium_flutter |
0.1.7 |
powersync |
2.1.0 |
printing |
5.14.2 |
simple_secure_storage_linux |
0.2.5 |
sqlcipher_flutter_libs |
0.6.8 |
sqlite3 |
2.9.4, 3.0.0, 3.3.0 |
sqlite3_flutter_libs |
0.5.30, 0.5.32, 0.5.34, 0.5.39, 0.5.41, 0.5.42, 0.6.0 |
objectbox_flutter_libs |
5.3.1, 5.3.2 |
objectbox_sync_flutter_libs |
5.3.1, 5.3.2 |
For the full list see foreign_deps/foreign_deps.json.
Offline / air-gapped use #
flutpak generate --no-foreign-deps
Pinning the registry version #
# flutpak.yaml
foreign-deps-ref: v0.6.1 # tag, branch, or SHA; default: main
Full config reference #
Config lives in one of two places (error if both exist):
| Location | Notes |
|---|---|
flutpak.yaml |
Recommended — standalone file |
pubspec.yaml |
flutpak: section — also works; note that flutpak uses kebab-case keys which look unusual alongside pubspec's own snake_case keys |
Complete YAML with all options #
# flutpak.yaml (recommended) — or flutpak: section in pubspec.yaml
output: flatpak # default
pub:
locks:
- pubspec.lock # default; add more if needed
flutter:
ref: "3.44.1" # tag, branch ("stable", "main"), or commit SHA
# omit for pure-Dart projects
patch: flatpak/patches/flutter/shared.sh.patch # optional custom patch
# Local overrides / additions for the foreign deps registry.
# Deep-merged on top of the remote registry before source resolution.
# Shorthand (no version key) resolves version from pubspec.lock:
foreign-deps:
sqlite3_flutter_libs:
manifest:
sources:
- type: patch
path: patches/sqlite3.patch
crlf: true # normalise to CRLF; key stripped from output
# Suppress a remote entry by providing an empty sources list:
some_package:
"1.2.3":
manifest:
sources: []
# Git ref used to fetch the foreign_deps registry (default: main):
foreign-deps-ref: main
# flutpak-specific overrides (not part of the Flatpak manifest schema):
repo-url: https://github.com/... # default: git remote get-url origin
disable-submodules: false # default: false (matches flatpak-builder default)
metainfo-path: app/share/metainfo/<app-id>.metainfo.xml # default
desktop-entry-path: app/share/applications/<app-id>.desktop # default
icons: # default: single 256x256 entry
- size: 256x256 # required when icons: key is present
path: app/share/icons/hicolor/256x256/apps/<app-id>.png
# optional additional sizes:
- size: 512x512
path: app/share/icons/hicolor/512x512/apps/<app-id>.png
- size: scalable
path: app/share/icons/hicolor/scalable/apps/<app-id>.svg
modules:
- flatpak/modules/bclibc.yml
app-id: io.github.YourOrg.YourApp # required
runtime-version: '25.08' # default: '25.08'
# For Flutter projects, org.freedesktop.Sdk.Extension.llvmXX is auto-injected
# based on runtime-version (25.08→llvm20, 24.08→llvm19, 23.08→llvm17).
# Add extra extensions manually if needed:
sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
# When the Flutter project lives in a subdirectory of the git repo:
subdir: apps/myapp # optional; emitted as subdir: on the app module
# Auto-detected if omitted (info printed on init):
command: yourapp # default: last segment of app-id
# Extra finish-args merged after the mandatory Flutter defaults:
finish-args:
- --filesystem=xdg-documents
- --share=network
# Verbatim flatpak source entries appended to the app module:
sources: []
env: {}
build-options:
append-path: /custom/bin
prepend-ld-library-path: /custom/lib
env:
MY_VAR: value
Field reference table #
| Field | Type | Default | Notes |
|---|---|---|---|
output |
string | flatpak |
Output directory for generated files |
pub.locks |
list | [pubspec.lock] |
Lock files to scan; $ENV vars expanded |
flutter.ref |
string | — | Flutter git ref (tag, branch, or SHA). When set, engine versions fetched from GitHub API — no local Flutter install needed for generate. Omit for pure-Dart projects. |
flutter.patch |
string | — | Custom patch for Flutter's shared.sh bootstrap script. Defaults to the built-in patch when omitted. |
foreign-deps |
map | {} |
Local overrides for the remote foreign deps registry. Deep-merged on top before resolution. See Local overrides and suppression. |
foreign-deps-ref |
string | main |
Git ref used to fetch the foreign deps registry. Pin to a tag or SHA for reproducible builds. |
repo-url |
string | git remote get-url origin |
Source git URL written into template |
disable-submodules |
bool | false |
When true, adds disable-submodules: true to the git source in the generated template, preventing flatpak-builder from cloning git submodules |
metainfo-path |
string | app/share/metainfo/<id>.metainfo.xml |
Validated on init and generate |
desktop-entry-path |
string | app/share/applications/<id>.desktop |
Validated on init and generate |
icons |
list | [{size: 256x256, path: app/share/icons/…}] |
Must include 256x256 if key is present |
modules |
list | [] |
Path strings or inline module maps injected as extra modules before the app module |
app-id |
string | required | Reverse-DNS app ID |
runtime-version |
string | 25.08 |
Freedesktop runtime version |
command |
string | last segment of app-id |
Executable name inside /app/bin/ |
subdir |
string | — | Subdirectory within the source tree where the build runs. Emitted as subdir: on the app module. Useful when the Flutter project lives in a subdirectory of the git repo. The generated stamp cp commands use $FLATPAK_BUILDER_BUILDDIR and are unaffected by subdir:. |
sdk-extensions |
list | [] |
For Flutter projects, llvmXX is auto-injected based on runtime-version (25.08 → llvm20, 24.08 → llvm19, 23.08 → llvm17). Add here only extensions beyond llvm (e.g. rust-stable). |
finish-args |
list | [] |
Extra sandbox permission flags appended after the mandatory Flutter defaults (--share=ipc, --socket=fallback-x11, --socket=wayland, --device=dri). Duplicates are silently removed. |
sources |
list | [] |
Verbatim flatpak source entries appended to the app module |
env |
map | {} |
Build-time env vars (shorthand) |
build-options.append-path |
string | — | Appended to PATH during build |
build-options.prepend-ld-library-path |
string | — | Prepended to LD_LIBRARY_PATH |
build-options.env |
map | {} |
Merged with top-level env: |
Note
Only the fields listed above are read from flutpak.yaml. Fields like
build-commands and the full modules tree are not read from config —
edit the template file (flatpak/<app-id>.yml) directly to change those.
Commands reference #
init #
flutpak init [--config <path>] [--flutter <ref>] [--force]
One-time setup. Generates the editable template manifest, wrapper script,
and flatpak/.gitignore, then immediately runs generate.
- Errors if the template (
flatpak/<app-id>.yml) already exists — use--forceto overwrite. - Validates that all asset files (metainfo, desktop entry, icons) exist.
- Auto-detects
repo-urlfromgit remote get-url origin.
| Flag | Description |
|---|---|
--config / -c |
Path to config file (default: auto-detected pubspec.yaml or flutpak.yaml) |
--flutter / -f |
Flutter git ref; overrides flutter.ref: in config |
--force |
Overwrite existing template and wrapper |
generate #
flutpak generate [--tag v1.2.3] [--commit sha] [--config <path>]
[--flutter <ref>] [--no-foreign-deps] [--dry-run]
Every-build command. Reads the committed template, validates it against
config, sets tag: and commit: in the git source block via yaml_edit,
generates generated-sources.json, and writes everything to flatpak/generated/.
- Errors if the template does not exist (run
flutpak initfirst). - Errors if
app-id,command, orruntime-versionin the template differ from config. - Errors if any asset file (metainfo, desktop entry, icon) does not exist.
- When
flutter.refis set (or--flutterpassed), fetchesflutter_tools/pubspec.lockfrom GitHub automatically and includes flutter_tools deps ingenerated-sources.json.
| Flag | Description |
|---|---|
--tag |
Git tag embedded in the manifest (e.g. v1.2.3) |
--commit |
Full git commit SHA; defaults to git rev-parse HEAD |
--config / -c |
Path to config file |
--flutter / -f |
Flutter git ref; overrides flutter.ref: in config |
--no-foreign-deps |
Skip foreign deps registry fetch (offline / air-gapped use) |
--dry-run / -n |
Print what would be written without writing any files |
sdk-mod #
flutpak sdk-mod [--flutter <ref>] [--output <dir>] [--patch <path>] [--config <path>]
Generates a standalone Flutter SDK module JSON that can be !include-d in
any Flatpak manifest or wrapped as an SDK Extension. The output module installs
Flutter to /var/lib/flutter.
# In your manifest:
modules:
- !include modules/flutter-sdk/flutter-sdk-3.44.1.json
- name: myapp
...
| Flag | Description |
|---|---|
--flutter / -f |
Flutter git ref (tag, branch, or SHA); overrides flutter.ref: in config |
--output / -o |
Output directory for the module JSON (default: modules/flutter-sdk) |
--patch |
Path to shared.sh.patch; defaults to built-in patch |
--config / -c |
Path to config file (default: flutpak.yaml) |
Pre-generated module JSONs for recent Flutter releases are available in
modules/flutter-sdk/.
--tag / --commit behavior #
--tag |
--commit |
Tag in manifest | Commit in manifest |
|---|---|---|---|
v1.2.3 |
— | v1.2.3 |
Resolved via git rev-parse v1.2.3^{} |
| — | abc123 |
abc123 |
abc123 |
| — | — | HEAD SHA | HEAD SHA |
v1.2.3 |
abc123 |
v1.2.3 |
abc123 (explicit override) |
When neither flag is passed, the current HEAD SHA is used for both values so
flatpak-builder can fetch and build the revision without a real release tag.
For release builds, always pass --tag. --tag requires the tag to exist
locally — use fetch-depth: 0 in your checkout step so the tag is available.
Manifest lifecycle #
The architecture separates the editable template (committed to git) from the generated output (gitignored):
flatpak/
├── <app-id>.yml <- editable template — committed to git
├── <name>-wrapper.sh <- committed to git
├── patches/ <- patch files — committed to git
├── .gitignore <- contains: generated/
└── generated/ <- gitignored, never commit this
├── <app-id>.yml <- final manifest (tag/commit set, sources injected)
├── generated-sources.json
└── patches/ <- copy
Phase 1 — flutpak init (run once)
Creates flatpak/<app-id>.yml — the editable template with guidance comments
and a git source block (no tag: / commit: yet). Commit this file — it is
the starting point that reviewers inspect and that generate reads on every
subsequent build.
Phase 2 — flutpak generate --tag vX.Y.Z (run on every CI build)
Reads the committed template, resolves the tag and commit SHA, sets tag: and
commit: directly in the git source block (via yaml_edit), generates
generated-sources.json, and copies everything to flatpak/generated/. The
template is never modified. flatpak-builder consumes
flatpak/generated/<app-id>.yml.
CI/CD integration #
setup-flutpak action #
- uses: o-murphy/flutpak/.github/actions/setup-flutpak@v0.6.1
# Builds from the same ref as the 'uses:' directive by default.
# Pin to a specific release:
# with:
# version: v
| Input | Description | Default |
|---|---|---|
version |
Release tag, commit SHA, or branch to compile | ref pinned in uses: directive |
install_dir |
Directory where the flutpak binary is placed |
/usr/local/bin |
The repository also ships a flutter composite action with
runner.arch-aware cache keys — it works correctly on both ubuntu-latest
(x86_64) and ubuntu-24.04-arm (aarch64) runners:
- uses: o-murphy/flutpak/.github/actions/flutter@v0.6.1
with:
flutter-version: stable # or read from flatpak/flutter.version
cache: true
| Input | Description | Default |
|---|---|---|
flutter-version |
Version tag (e.g. 3.41.6), stable, or beta |
stable |
cache |
Cache the SDK between runs | true |
| Output | Description |
|---|---|
flutter-path |
Absolute path to the installed Flutter SDK |
The action also exports FLUTTER_ROOT and FLUTTER_HOME env vars and adds
flutter/bin to $PATH, so flutter pub get and flutter build work
without additional configuration.
generate action #
Runs flutpak generate, validates metainfo, and uploads the generated manifest
and sources as a workflow artifact. Flutter is still needed in CI for
flutter pub get / flutter build; engine version files are fetched from
GitHub via flutter-ref.
- uses: o-murphy/flutpak/.github/actions/generate@v0.7.0
with:
tag: ${{ inputs.tag }} # or pass 'commit:' for non-tag builds
# commit: ${{ github.sha }} # used when 'tag' is empty
flutter-ref: "3.44.1" # optional; overrides flutter.ref from config
metainfo-path: app/share/metainfo/<app-id>.metainfo.xml
artifact-name: flatpak-generated
| Input | Description | Default |
|---|---|---|
tag |
Git tag to embed in the manifest (mutually exclusive with commit) |
— |
commit |
Commit SHA; defaults to github.sha when empty |
— |
flutter-ref |
Flutter git ref; overrides flutter.ref: in config |
— |
metainfo-path |
Path to .metainfo.xml; appstream is installed automatically if needed |
— |
artifact-name |
Upload generated files under this artifact name | flutpak-artifacts |
retention-days |
Artifact retention in days | 2 |
flutpak-version |
flutpak version to install; defaults to the same ref as the uses: directive |
— |
build-flatpak action #
Installs org.flatpak.Builder, lints the manifest, builds via flathub-build,
lints the repo, exports a .flatpak bundle, and optionally uploads it.
- uses: o-murphy/flutpak/.github/actions/build-flatpak@v0.6.1
id: build
with:
manifest: flatpak/generated/<app-id>.yml
app-id: io.github.YourOrg.YourApp
artifact-name: myapp-flatpak
github-token: ${{ secrets.GITHUB_TOKEN }} # for private source downloads
# Downstream steps can reference:
# ${{ steps.build.outputs.bundle }} — path to the .flatpak file
# ${{ steps.build.outputs.arch }} — x86_64 or aarch64
# ${{ steps.build.outputs.artifact-url }} — download URL of the uploaded artifact
| Input | Description | Default |
|---|---|---|
manifest |
Path to the generated Flatpak manifest | required |
app-id |
Flatpak application ID | required |
arch |
Target architecture (x86_64 or aarch64); auto-detected from flatpak --default-arch |
— |
bundle |
Output .flatpak filename |
<app-id>_<arch>.flatpak |
artifact-name |
Upload bundle under this artifact name; skip upload if empty | — |
retention-days |
Artifact retention in days | 2 |
github-token |
GitHub token written to ~/.netrc for private source downloads |
— |
| Output | Description |
|---|---|
bundle |
Path to the exported .flatpak bundle |
arch |
Architecture used for the build |
artifact-url |
Download URL of the uploaded artifact (empty if artifact-name is unset) |
Release workflow example #
The generate and build-flatpak actions let you split generation (runs once)
from building (can fan out across multiple architectures):
name: Flatpak release
on:
push:
tags: ['v*']
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # required for --tag to resolve the commit
- uses: o-murphy/flutpak/.github/actions/flutter@v0.6.1
id: flutter
with:
flutter-version: stable
cache: true
- run: flutter pub get
- uses: o-murphy/flutpak/.github/actions/generate@v0.6.1
with:
tag: ${{ github.ref_name }}
metainfo-path: app/share/metainfo/<app-id>.metainfo.xml
artifact-name: flatpak-generated
build:
needs: generate
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
steps:
- uses: actions/download-artifact@v8
with:
name: flatpak-generated
path: flatpak/generated
- uses: o-murphy/flutpak/.github/actions/build-flatpak@v0.6.1
id: build
with:
manifest: flatpak/generated/<app-id>.yml
app-id: io.github.YourOrg.YourApp
artifact-name: myapp-${{ matrix.arch }}
github-token: ${{ secrets.GITHUB_TOKEN }}
For a minimal single-job workflow that uses setup-flutpak + manual steps, see
the legacy example below.
Legacy single-job example (manual steps)
name: Flatpak release (manual)
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: o-murphy/flutpak/.github/actions/setup-flutpak@v0.6.1
- uses: o-murphy/flutpak/.github/actions/flutter@v0.6.1
with:
flutter-version: stable
cache: true
- run: flutter pub get
- run: flutpak generate --tag ${{ github.ref_name }}
Validating and building locally #
flutpak generates sources and manifests. The actual lint, build, and
validation steps use the official Flatpak toolchain.
Install toolchain #
# Install appstreamcli (Debian/Ubuntu)
sudo apt-get install appstream
# Install org.flatpak.Builder (required for build + lint)
flatpak remote-add --user --if-not-exists flathub \
https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install --user flathub org.flatpak.Builder
# Install the Freedesktop runtime
flatpak install flathub org.freedesktop.Sdk//25.08
1. Validate metainfo #
appstreamcli validate --explain --no-net \
app/share/metainfo/<app-id>.metainfo.xml
2. Lint the manifest #
dbus-run-session flatpak run \
--filesystem=host \
--command=flatpak-builder-lint \
org.flatpak.Builder \
--exceptions \
manifest flatpak/generated/<app-id>.yml
--exceptions applies the built-in Flathub exception list. Run this before
building to catch manifest errors early.
Important
Do not set a top-level branch: in your manifest —
flatpak-builder-lint flags this as toplevel-unnecessary-branch.
3. Build with flathub-build #
flathub-build is the same script Flathub CI uses. It runs flatpak-builder
with --sandbox, --install-deps-from=flathub, --repo=repo, and other
Flathub-specific flags.
dbus-run-session flatpak run --command=flathub-build \
org.flatpak.Builder \
flatpak/generated/<app-id>.yml
4. Lint the built repo #
dbus-run-session flatpak run \
--filesystem=host \
--command=flatpak-builder-lint \
org.flatpak.Builder \
--exceptions \
repo repo
5. Export a single-file bundle (optional) #
flatpak build-bundle repo myapp.flatpak <app-id>
flatpak install --user myapp.flatpak
flatpak run <app-id>
How it works #
Pub sources #
For each hosted package in your lock file(s), flutpak fetches the SHA-256
checksum from the pub.dev API and generates two flatpak-builder source
entries:
[
{
"type": "archive",
"url": "https://pub.dev/packages/yaml/versions/3.1.2.tar.gz",
"sha256": "abc123...",
"dest": ".pub-cache/hosted/pub.dev/yaml-3.1.2",
"strip-components": 0
},
{
"type": "inline",
"contents": "abc123...",
"dest": ".pub-cache/hosted-hashes/pub.dev",
"dest-filename": "yaml-3.1.2.sha256"
}
]
Both entries are required: pub get --offline checks the hash file and fails
if it is missing even when the archive is present.
Flutter SDK sources #
When flutter.ref is set, flutpak fetches the engine version files directly
from GitHub raw API — no local Flutter installation needed:
raw.githubusercontent.com/flutter/flutter/{ref}/bin/internal/engine.version
raw.githubusercontent.com/flutter/flutter/{ref}/bin/internal/material_fonts.version
raw.githubusercontent.com/flutter/flutter/{ref}/bin/internal/gradle_wrapper.version
raw.githubusercontent.com/flutter/flutter/{ref}/packages/flutter_tools/pubspec.lock
From these it constructs download URLs for each artifact — Dart SDK, engine
binaries, fonts, and the Gradle wrapper — for both x86_64 and aarch64.
SHA-256 checksums are cached in ~/.cache/flutpak/ by URL hash.
The flutter_tools/pubspec.lock is fetched automatically and written to
generated/flutter_tools.lock; its packages are included in
generated-sources.json so the Flutter toolchain can bootstrap itself offline
inside the Flatpak sandbox.
All sources (pub + Flutter SDK) are combined into a single
generated-sources.json. This matches the convention expected by Flathub
reviewers — splitting into multiple files offers no practical benefit and
typically prompts questions during review.
Two extra entries are always added:
sky_engine/pubspec.yaml(inline) —packages/sky_engine/was removed from the Flutter git tree in Flutter 3.x. Written inline sopub get --offlineresolves it.shared.sh.patch— patches Flutter's bootstrap script to usepub upgrade --offlineinstead of the network-requiringpub upgrade.
Foreign deps registry #
Source injection happens at generate time, not init time. The template
manifest contains no type: patch or foreign-dep entries — this is by design.
On every generate run, flutpak:
- Fetches
foreign_deps/foreign_deps.jsonfrom GitHub (cached in~/.cache/flutpak/). - Deep-merges any local
foreign-deps:entries fromflutpak.yamlon top. - Resolves the current package versions from
pubspec.lock. - Downloads patch files to
generated/patches/<path>. - Appends all resolved sources to
generated-sources.json.
crlf: true on a type: patch source normalises the patch file to CRLF line
endings when writing to generated/patches/; the key is stripped before
writing to generated-sources.json. Patches without crlf: true are
normalised to LF, making output deterministic on any host OS.
Note
Add *.patch -text to your project's .gitattributes to prevent git from
re-normalising line endings in patch files on Windows checkouts.
Wrapper script #
For Flutter apps, init generates flatpak/<name>-wrapper.sh:
#!/bin/sh
# Generated by flutpak — https://github.com/o-murphy/flutpak
APP=/app/<name>
export LD_LIBRARY_PATH="$APP/lib:${LD_LIBRARY_PATH:-}"
exec "$APP/<name>" "$@"
This bridges Flatpak's command: entry (pointing to /app/bin/<name>) to the
Flutter bundle at /app/<name>/<name>, setting LD_LIBRARY_PATH for shared
libraries bundled with the app.
Template vs generated #
The template (flatpak/<app-id>.yml) is a standard Flatpak manifest that you
commit to git. It contains no placeholder strings — generate writes tag: and
commit: directly into the git source block via yaml_edit.
generate copies the template to flatpak/generated/<app-id>.yml, sets tag:
and commit: in the git source block, and appends generated-sources.json
to the app module sources. Foreign dep sources (patches, archives) are resolved
from pubspec.lock at generate time and appended to generated-sources.json —
never baked into the template. The template is never modified; the generated
file is gitignored and rebuilt on every CI run.
generate also validates consistency between the template and config before
writing anything:
app-idin template must matchapp-idin configcommandin template must matchcommandin config (or its default)runtime-versionin template must matchruntime-versionin config
Building from source / contributing #
git clone https://github.com/o-murphy/flutpak.git
cd flutpak
make test # run test suite
make build # compile binary (version from git describe --tags)
Bumping the version #
- Update
version:inpubspec.yaml - Commit and tag (
git tag v1.2.3) —make buildpicks the version from the tag automatically viagit describe
During a release, release.yml performs step 1 automatically when a v*
tag is pushed, so the compiled binaries always report the correct version.
License #
MIT