build: publish linux flatpak deb and rpm artifacts

Restore Tauri Debian and RPM assets while adding Flatpak outputs for both desktop variants so Linux users have package-manager and sandboxed alternatives to AppImage.

Electron Flatpak packaging now installs the required Freedesktop runtime in CI, and Tauri Flatpak packaging uses a generated manifest around the existing release binary and packaged resources. The Linux dependency list also includes xdg-utils because Tauri AppImage bundling requires xdg-open.

Docker-based Ubuntu 24.04 reproductions verified Electron zip/AppImage creation and Tauri AppImage/deb/rpm creation. Local Docker could not complete Flatpak builds because bubblewrap cannot create/seccomp namespaces in this host container environment, which should not apply to native GitHub runners.
This commit is contained in:
Shantur Rathore 2026-05-20 22:16:25 +01:00
parent cabe89476f
commit 3de5ecb7e9
6 changed files with 161 additions and 6 deletions

View file

@ -311,6 +311,16 @@ jobs:
- name: Ensure rollup native binary
run: npm install @rollup/rollup-linux-x64-gnu --no-save
- name: Install Flatpak build dependencies (Electron)
run: |
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
sudo flatpak remote-add --system --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
sudo flatpak install --system --noninteractive flathub \
org.freedesktop.Platform//24.08 \
org.freedesktop.Sdk//24.08 \
org.electronjs.Electron2.BaseApp//24.08
- name: Build Linux binaries (Electron)
run: npm run build:linux --workspace @neuralnomads/codenomad-electron-app
@ -322,7 +332,7 @@ jobs:
run: |
set -euo pipefail
shopt -s nullglob
for file in packages/electron-app/release/*.zip packages/electron-app/release/*.AppImage; do
for file in packages/electron-app/release/*.zip packages/electron-app/release/*.AppImage packages/electron-app/release/*.flatpak; do
[ -f "$file" ] || continue
echo "Uploading $file"
gh release upload "$TAG" "$file" --clobber
@ -336,6 +346,7 @@ jobs:
path: |
packages/electron-app/release/*.zip
packages/electron-app/release/*.AppImage
packages/electron-app/release/*.flatpak
retention-days: ${{ inputs.actions_artifacts_retention_days }}
if-no-files-found: error
@ -637,7 +648,15 @@ jobs:
libwebkit2gtk-4.1-dev \
libsoup-3.0-dev \
libayatana-appindicator3-dev \
librsvg2-dev
librsvg2-dev \
xdg-utils \
rpm \
flatpak \
flatpak-builder
sudo flatpak remote-add --system --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
sudo flatpak install --system --noninteractive flathub \
org.gnome.Platform//46 \
org.gnome.Sdk//46
- name: Set workspace versions
if: ${{ inputs.set_versions && inputs.version != '' }}
@ -673,6 +692,9 @@ jobs:
working-directory: packages/tauri-app
run: npm exec -- tauri build
- name: Build Flatpak bundle (Tauri)
run: npm run build:flatpak --workspace @codenomad/tauri-app
- name: Package Tauri artifacts (Linux)
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
run: |
@ -688,14 +710,20 @@ jobs:
}
appimage=$(find_one "*.AppImage")
deb=$(find_one "*.deb")
rpm=$(find_one "*.rpm")
flatpak=$(find_one "*.flatpak")
fallback_bin="$SEARCH_ROOT/release/codenomad-tauri"
if [ -z "$appimage" ] || [ ! -f "$fallback_bin" ]; then
echo "Missing bundle(s): appimage=${appimage:-none} binary=$fallback_bin" >&2
if [ -z "$appimage" ] || [ -z "$deb" ] || [ -z "$rpm" ] || [ -z "$flatpak" ] || [ ! -f "$fallback_bin" ]; then
echo "Missing bundle(s): appimage=${appimage:-none} deb=${deb:-none} rpm=${rpm:-none} flatpak=${flatpak:-none} binary=$fallback_bin" >&2
exit 1
fi
cp "$appimage" "$ARTIFACT_DIR/CodeNomad-Tauri-linux-x64-${VERSION}.AppImage"
cp "$deb" "$ARTIFACT_DIR/CodeNomad-Tauri-linux-x64-${VERSION}.deb"
cp "$rpm" "$ARTIFACT_DIR/CodeNomad-Tauri-linux-x64-${VERSION}.rpm"
cp "$flatpak" "$ARTIFACT_DIR/CodeNomad-Tauri-linux-x64-${VERSION}.flatpak"
zip -j "$ARTIFACT_DIR/CodeNomad-Tauri-linux-x64-${VERSION}.zip" "$fallback_bin"
- name: Upload Actions artifacts (Tauri Linux)

View file

@ -137,11 +137,30 @@
},
{
"target": "AppImage"
},
{
"target": "flatpak"
}
],
"artifactName": "CodeNomad-Electron-linux-${arch}-${version}.${ext}",
"category": "Development",
"icon": "electron/resources/icon.png"
},
"flatpak": {
"runtimeVersion": "24.08",
"baseVersion": "24.08",
"branch": "stable",
"useWaylandFlags": true,
"finishArgs": [
"--socket=wayland",
"--socket=x11",
"--share=ipc",
"--device=dri",
"--socket=pulseaudio",
"--filesystem=home",
"--share=network",
"--talk-name=org.freedesktop.Notifications"
]
}
},
"private": true

View file

@ -10,7 +10,7 @@
"README.md"
],
"scripts": {
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json"
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && npm exec -- tsc -p tsconfig.json"
},
"dependencies": {
"@opencode-ai/plugin": "1.3.7"

View file

@ -11,6 +11,7 @@
"sync:version": "node ./scripts/sync-tauri-version.js",
"prebuild": "node ./scripts/prebuild.js",
"bundle:server": "npm run prebuild",
"build:flatpak": "node ./scripts/build-flatpak.js",
"build": "tauri build",
"smoke:resources": "node ../../scripts/smoke-packaged-resources.cjs --resources src-tauri/resources --loading src-tauri/resources/ui-loading"
},

View file

@ -0,0 +1,107 @@
#!/usr/bin/env node
const fs = require("fs")
const path = require("path")
const { spawnSync } = require("child_process")
const root = path.resolve(__dirname, "..")
const workspaceRoot = path.resolve(root, "..", "..")
const version = process.env.VERSION || require(path.join(root, "package.json")).version
const appId = "ai.neuralnomads.codenomad.client"
const buildRoot = path.join(root, "target", "flatpak")
const stagingRoot = path.join(buildRoot, "staging")
const repoRoot = path.join(buildRoot, "repo")
const flatpakBuildRoot = path.join(buildRoot, "build")
const manifestPath = path.join(buildRoot, `${appId}.json`)
const artifactDir = path.join(root, "target", "release", "bundle", "flatpak")
const artifactPath = path.join(artifactDir, `CodeNomad-Tauri-linux-x64-${version}.flatpak`)
function run(command, args, options = {}) {
const result = spawnSync(command, args, { stdio: "inherit", ...options })
if (result.error) throw result.error
if (result.status !== 0) {
throw new Error(`${command} ${args.join(" ")} exited with code ${result.status || 1}`)
}
}
function copyRequired(from, to) {
if (!fs.existsSync(from)) {
throw new Error(`Missing required Flatpak input: ${from}`)
}
fs.cpSync(from, to, { recursive: true, dereference: true })
}
fs.rmSync(buildRoot, { recursive: true, force: true })
fs.mkdirSync(stagingRoot, { recursive: true })
fs.mkdirSync(artifactDir, { recursive: true })
copyRequired(path.join(root, "target", "release", "codenomad-tauri"), path.join(stagingRoot, "codenomad-tauri"))
copyRequired(path.join(root, "target", "release", "resources"), path.join(stagingRoot, "resources"))
copyRequired(
path.join(root, "src-tauri", "icons", "linux", "512x512.png"),
path.join(stagingRoot, `${appId}.png`),
)
const desktopFile = [
"[Desktop Entry]",
"Type=Application",
"Name=CodeNomad",
"Comment=AI coding assistant",
"Categories=Development;IDE;",
"Exec=codenomad-tauri",
`Icon=${appId}`,
"Terminal=false",
"",
].join("\n")
fs.writeFileSync(path.join(stagingRoot, `${appId}.desktop`), desktopFile)
const manifest = {
"app-id": appId,
runtime: "org.gnome.Platform",
"runtime-version": "46",
sdk: "org.gnome.Sdk",
branch: "stable",
command: "codenomad-tauri",
"finish-args": [
"--socket=wayland",
"--socket=x11",
"--share=ipc",
"--device=dri",
"--socket=pulseaudio",
"--filesystem=home",
"--share=network",
"--talk-name=org.freedesktop.Notifications",
],
modules: [
{
name: "codenomad-tauri",
"buildsystem": "simple",
"build-commands": [
"install -Dm755 codenomad-tauri /app/bin/codenomad-tauri",
"mkdir -p /app/lib/CodeNomad",
"cp -a resources /app/lib/CodeNomad/resources",
`install -Dm644 ${appId}.desktop /app/share/applications/${appId}.desktop`,
`install -Dm644 ${appId}.png /app/share/icons/hicolor/512x512/apps/${appId}.png`,
],
sources: [
{
type: "dir",
path: stagingRoot,
},
],
},
],
}
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
fs.rmSync(repoRoot, { recursive: true, force: true })
fs.rmSync(flatpakBuildRoot, { recursive: true, force: true })
fs.rmSync(artifactPath, { force: true })
run("flatpak-builder", ["--force-clean", `--repo=${repoRoot}`, flatpakBuildRoot, manifestPath], {
cwd: workspaceRoot,
})
run("flatpak", ["build-bundle", repoRoot, artifactPath, appId, "stable"], {
cwd: workspaceRoot,
})
console.log(`[flatpak] wrote ${artifactPath}`)

View file

@ -23,7 +23,7 @@ function copyRequiredArtifact(serverRoot, serverDest, name, log) {
if (!fs.existsSync(from)) {
throw new Error(`Missing required server artifact: ${from}`)
}
fs.cpSync(from, to, { recursive: true, dereference: true })
fs.cpSync(from, to, { recursive: true })
log(`copied ${name}`)
}