feat: add Flatpak packaging

- Add Flatpak manifest (com.opengsd.pi.yaml) using GNOME 48 SDK
- Add tauri-plugin-localhost to serve bundled assets via HTTP since
  the tauri:// custom protocol doesn't work in WebKit2GTK Flatpak
  sandbox
- Use npx tauri build (not cargo build) to properly embed frontend
  assets into the binary
- Vendor npm + Cargo deps on host to work around DNS resolution
  failure in bubblewrap sandbox (127.0.0.53 unreachable)
- Set WEBKIT_DISABLE_DMABUF_RENDERER=1 to fix Wayland crash
- Add desktop entry, AppStream metadata, and SVG icon
- Add build.sh and create-bundle.sh helper scripts
- Update README with Flatpak build instructions
This commit is contained in:
John Smith
2026-06-02 12:16:45 -04:00
parent 2a63f34db8
commit b40e19f799
14 changed files with 388 additions and 13 deletions
+36
View File
@@ -28,3 +28,39 @@ Thumbs.db
.idea/
*.swp
.vercel
# ── GSD baseline (auto-generated) ──
.gsd
.gsd-id
.mcp.json
.bg-shell/
nul
nul.*
con
con.*
prn
prn.*
aux
aux.*
com[1-9]
com[1-9].*
lpt[1-9]
lpt[1-9].*
*.swo
*~
*.code-workspace
.env.*
!.env.example
.next/
__pycache__/
*.pyc
.venv/
venv/
target/
vendor/
coverage/
.cache/
tmp/
.agents/
flatpak/cargo-vendor/
+15
View File
@@ -91,6 +91,21 @@ npm run tauri build
Bundles land in `src-tauri/target/release/bundle/`.
### Build a Flatpak
```bash
cd flatpak
./build.sh
```
This vendors all npm and Cargo dependencies on the host (required because the Flatpak sandbox can't resolve DNS), then builds inside the sandbox with `npx tauri build --no-bundle` so frontend assets are properly embedded. The `tauri-plugin-localhost` serves the bundled assets at runtime since the `tauri://` custom protocol doesn't work inside WebKit2GTK in the Flatpak sandbox.
```bash
flatpak run com.opengsd.pi
```
See `flatpak/README.md` for prerequisites and details.
## Keyboard shortcuts
| Shortcut | Action |
+71
View File
@@ -0,0 +1,71 @@
# Flatpak Build
This directory contains the Flatpak packaging for Pi.
## Files
- `com.opengsd.pi.yaml` - Flatpak manifest (uses GNOME 48 SDK for webkit2gtk-4.1)
- `com.opengsd.pi.desktop` - Desktop entry file
- `com.opengsd.pi.metainfo.xml` - AppStream metadata
- `com.opengsd.pi.svg` - Application icon
- `cargo-config.toml` - Cargo config for vendored dependencies
- `build.sh` - Vendor crates + build + install locally
- `create-bundle.sh` - Create bundle for distribution
## Why vendored crates?
The flatpak-builder sandbox isolates loopback networking, which breaks
DNS resolution when `/etc/resolv.conf` points to `127.0.0.53`
(systemd-resolved stub). Rather than modifying system DNS, we vendor
all Cargo crates on the host and build with `--offline`.
## Prerequisites
```bash
# Install flatpak and flatpak-builder
sudo apt install flatpak flatpak-builder
# Add Flathub
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
# Install GNOME SDK (includes webkit2gtk-4.1)
flatpak install flathub org.gnome.Sdk//48 org.gnome.Platform//48
```
## Build & Install Locally
```bash
cd flatpak
./build.sh
```
## Run
```bash
flatpak run com.opengsd.pi
```
## Create Bundle for Distribution
```bash
./create-bundle.sh
```
## Notes
- Uses `org.gnome.Platform//48` runtime (includes webkit2gtk-4.1, GTK3, libsoup)
- Rust SDK extension is included via `append-path`
- Keyring access via `--filesystem=xdg-run/keyring`
- Network access enabled for updater functionality
- Binary is renamed from `gsd-pi-config` to `pi` in the Flatpak
## Flathub Submission
For Flathub submission, you'll need to:
1. Fork the flathub/com.opengsd.pi repository
2. Update the manifest to use git source instead of local dir
3. Add GPG signing
4. Submit PR to Flathub
See: https://docs.flathub.org/flatpak-prerequisites/
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
# Build script for Flatpak
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
echo "=== Vendoring npm dependencies ==="
cd "$PROJECT_DIR"
rm -rf node_modules
npm ci 2>&1 | tail -5
echo ""
echo "=== Vendoring Cargo crates ==="
rm -rf flatpak/cargo-vendor
cargo vendor --versioned-dirs --manifest-path src-tauri/Cargo.toml flatpak/cargo-vendor 2>&1 | tail -3
echo ""
echo "=== Building Flatpak ==="
cd "$SCRIPT_DIR"
rm -rf .flatpak-builder build-dir
flatpak-builder --user --install --force-clean build-dir com.opengsd.pi.yaml
echo ""
echo "Done! Run with: flatpak run com.opengsd.pi"
+5
View File
@@ -0,0 +1,5 @@
[source.crates-io]
replace-with = "vendored-sources"
[source.vendored-sources]
directory = "src-tauri/vendor"
+7
View File
@@ -0,0 +1,7 @@
[Desktop Entry]
Name=Pi
Exec=pi
Icon=com.opengsd.pi
Type=Application
Categories=Development;Utility;
Keywords=tauri;desktop;config;
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.opengsd.pi</id>
<metadata_license>MIT</metadata_license>
<project_license>MIT</project_license>
<name>Pi</name>
<summary>A Tauri v2 desktop application</summary>
<description>
<p>
Pi is a desktop application built with Tauri v2.
It provides a modern, secure interface for configuration and management.
</p>
</description>
<url type="homepage">https://github.com/open-gsd/gsd-pi-config</url>
<url type="bugtracker">https://github.com/open-gsd/gsd-pi-config/issues</url>
<provides>
<binary>pi</binary>
</provides>
<content_rating type="oars-1.1" />
<releases>
<release version="0.1.0" date="2026-06-02">
<description>
<p>Initial release with basic functionality.</p>
</description>
</release>
</releases>
</component>
+50
View File
@@ -0,0 +1,50 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
<defs>
<!-- Background gradient: deep black with subtle cyan tint at top -->
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#0a1420"/>
<stop offset="40%" stop-color="#000000"/>
<stop offset="100%" stop-color="#000000"/>
</linearGradient>
<!-- Cyan glow filter -->
<filter id="cyanGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="28" result="blur"/>
<feFlood flood-color="#7dcfff" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in" result="glow"/>
<feMerge>
<feMergeNode in="glow"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<!-- Inner stroke highlight -->
<linearGradient id="strokeGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgba(125, 207, 255, 0.3)"/>
<stop offset="100%" stop-color="rgba(125, 207, 255, 0.05)"/>
</linearGradient>
<style>
.wordmark {
font-family: 'SF Mono', 'JetBrains Mono', 'Fira Code', 'Menlo', monospace;
font-weight: 800;
letter-spacing: -0.04em;
fill: #7dcfff;
}
</style>
</defs>
<!-- Rounded square background (macOS convention ~22% corner radius) -->
<rect x="0" y="0" width="1024" height="1024" rx="224" ry="224" fill="url(#bgGrad)"/>
<!-- Inner stroke for depth -->
<rect x="4" y="4" width="1016" height="1016" rx="220" ry="220"
fill="none" stroke="url(#strokeGrad)" stroke-width="2"/>
<!-- Giant "2" with glow, perfectly centered -->
<g filter="url(#cyanGlow)">
<text class="wordmark" x="512" y="720" font-size="760" text-anchor="middle">
2
</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+66
View File
@@ -0,0 +1,66 @@
app-id: com.opengsd.pi
runtime: org.gnome.Platform
runtime-version: '48'
sdk: org.gnome.Sdk
command: pi
finish-args:
- --share=ipc
- --socket=fallback-x11
- --socket=wayland
- --socket=pulseaudio
- --device=dri
- --filesystem=xdg-run/keyring
- --share=network
- --env=WEBKIT_DISABLE_DMABUF_RENDERER=1
build-options:
env:
CARGO_HOME: /run/build/pi/cargo
modules:
# Node.js runtime
- name: nodejs
buildsystem: simple
build-commands:
- mkdir -p /app/share/nodejs
- mv * /app/share/nodejs/
- mkdir -p /app/bin
- ln -s /app/share/nodejs/bin/node /app/bin/node
- ln -s /app/share/nodejs/bin/npm /app/bin/npm
- ln -s /app/share/nodejs/bin/npx /app/bin/npx
sources:
- type: archive
url: https://nodejs.org/dist/v22.16.0/node-v22.16.0-linux-x64.tar.xz
sha256: f4cb75bb036f0d0eddf6b79d9596df1aaab9ddccd6a20bf489be5abe9467e84e
# Build everything in one module (frontend + Tauri with asset embedding)
- name: pi
buildsystem: simple
build-options:
append-path: /app/share/nodejs/bin:/usr/lib/sdk/rust-stable/bin
build-commands:
# Build frontend
- npm run build
# Build with tauri — this embeds frontend assets into the binary
- mkdir -p .cargo
- cp cargo-config.toml .cargo/config.toml
- CARGO_NET_OFFLINE=true npx tauri build --no-bundle
# Install binary
- mkdir -p ${FLATPAK_DEST}/bin
- cp src-tauri/target/release/gsd-pi-config ${FLATPAK_DEST}/bin/pi
# Install desktop/icon/metadata
- mkdir -p ${FLATPAK_DEST}/share/applications
- cp flatpak/com.opengsd.pi.desktop ${FLATPAK_DEST}/share/applications/
- mkdir -p ${FLATPAK_DEST}/share/icons/hicolor/scalable/apps
- cp flatpak/com.opengsd.pi.svg ${FLATPAK_DEST}/share/icons/hicolor/scalable/apps/
- mkdir -p ${FLATPAK_DEST}/share/metainfo
- cp flatpak/com.opengsd.pi.metainfo.xml ${FLATPAK_DEST}/share/metainfo/
sources:
- type: dir
path: ..
- type: dir
path: cargo-vendor
dest: src-tauri/vendor
- type: file
path: cargo-config.toml
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# Create a release bundle for Flathub submission
set -e
cd "$(dirname "$0")"
# Build
flatpak-builder --repo=repo --force-clean build-dir com.opengsd.pi.yaml
# Create bundle
flatpak build-bundle repo com.opengsd.pi.flatpak com.opengsd.pi
echo "Bundle created: flatpak/com.opengsd.pi.flatpak"
echo "Upload to Flathub or host on your own repo."
+56
View File
@@ -90,6 +90,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "ascii"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]]
name = "async-broadcast"
version = "0.7.2"
@@ -575,6 +581,12 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "chunked_transfer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
[[package]]
name = "combine"
version = "4.6.7"
@@ -1558,12 +1570,14 @@ dependencies = [
"dirs",
"keyring",
"log",
"portpicker",
"serde",
"serde_json",
"serde_yaml",
"tauri",
"tauri-build",
"tauri-plugin-dialog",
"tauri-plugin-localhost",
"tauri-plugin-log",
"tauri-plugin-opener",
"tauri-plugin-process",
@@ -1732,6 +1746,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "1.9.0"
@@ -2966,6 +2986,15 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "portpicker"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9"
dependencies = [
"rand 0.8.5",
]
[[package]]
name = "potential_utf"
version = "0.1.5"
@@ -4383,6 +4412,21 @@ dependencies = [
"url",
]
[[package]]
name = "tauri-plugin-localhost"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c8d72c024121b1a3d9268293d49a56baf01a8c785561c85d17872588b839e55"
dependencies = [
"http",
"log",
"serde",
"serde_json",
"tauri",
"thiserror 2.0.18",
"tiny_http",
]
[[package]]
name = "tauri-plugin-log"
version = "2.8.0"
@@ -4677,6 +4721,18 @@ dependencies = [
"time-core",
]
[[package]]
name = "tiny_http"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
dependencies = [
"ascii",
"chunked_transfer",
"httpdate",
"log",
]
[[package]]
name = "tinystr"
version = "0.8.3"
+2
View File
@@ -28,6 +28,8 @@ tauri-plugin-updater = "2"
tauri-plugin-process = "2"
dirs = "6"
keyring = { version = "3", features = ["apple-native", "linux-native", "windows-native"] }
tauri-plugin-localhost = "2.3.2"
portpicker = "0.1.1"
[dev-dependencies]
tempfile = "3"
+12 -1
View File
@@ -966,7 +966,18 @@ pub fn run() {
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_process::init())
.setup(|app| {
.plugin(tauri_plugin_localhost::Builder::new(5173).build())
.setup(move |app| {
#[cfg(not(dev))]
{
let url: tauri::Url = "http://localhost:5173".parse().unwrap();
tauri::WebviewWindowBuilder::new(app, "main".to_string(), tauri::WebviewUrl::External(url))
.title("GSD Pi Config")
.inner_size(1100.0, 750.0)
.min_inner_size(800.0, 600.0)
.resizable(true)
.build()?;
}
if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
+1 -12
View File
@@ -10,18 +10,7 @@
"beforeBuildCommand": "npm run build"
},
"app": {
"windows": [
{
"title": "GSD Pi Config",
"width": 1100,
"height": 750,
"minWidth": 800,
"minHeight": 600,
"resizable": true,
"fullscreen": false,
"decorations": true
}
],
"windows": [],
"security": {
"csp": null
}