From 3de5ecb7e90d017a070decffa4415d15448f48b2 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Wed, 20 May 2026 22:16:25 +0100 Subject: [PATCH] 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. --- .github/workflows/build-and-upload.yml | 36 ++++++- packages/electron-app/package.json | 19 ++++ packages/opencode-plugin/package.json | 2 +- packages/tauri-app/package.json | 1 + packages/tauri-app/scripts/build-flatpak.js | 107 ++++++++++++++++++++ scripts/desktop-server-resources.cjs | 2 +- 6 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 packages/tauri-app/scripts/build-flatpak.js diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml index 2dd97162..a703297e 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/build-and-upload.yml @@ -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) diff --git a/packages/electron-app/package.json b/packages/electron-app/package.json index f6a1fe75..038226be 100644 --- a/packages/electron-app/package.json +++ b/packages/electron-app/package.json @@ -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 diff --git a/packages/opencode-plugin/package.json b/packages/opencode-plugin/package.json index a77516bb..7c9d2cb7 100644 --- a/packages/opencode-plugin/package.json +++ b/packages/opencode-plugin/package.json @@ -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" diff --git a/packages/tauri-app/package.json b/packages/tauri-app/package.json index 590a3a4c..d496c925 100644 --- a/packages/tauri-app/package.json +++ b/packages/tauri-app/package.json @@ -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" }, diff --git a/packages/tauri-app/scripts/build-flatpak.js b/packages/tauri-app/scripts/build-flatpak.js new file mode 100644 index 00000000..413a637f --- /dev/null +++ b/packages/tauri-app/scripts/build-flatpak.js @@ -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}`) diff --git a/scripts/desktop-server-resources.cjs b/scripts/desktop-server-resources.cjs index 000219cd..c906b5a8 100644 --- a/scripts/desktop-server-resources.cjs +++ b/scripts/desktop-server-resources.cjs @@ -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}`) }