From 728c1a87739f639bd4c2f6ba9e67ea98f29c3ade Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 2 May 2026 06:15:41 -0800 Subject: [PATCH 1/5] testing --- .github/renovate.json5 | 2 +- .github/workflows/flatpak-pages.yml | 108 +++++++++++++++++++++++ .github/workflows/release.yml | 21 ++++- README.md | 16 +++- flatpak/app.winboat.WinBoat.desktop | 10 +++ flatpak/app.winboat.WinBoat.metainfo.xml | 43 +++++++++ flatpak/app.winboat.WinBoat.yml | 50 +++++++++++ flatpak/pages-index.html | 28 ++++++ flatpak/winboat.sh | 8 ++ 9 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/flatpak-pages.yml create mode 100644 flatpak/app.winboat.WinBoat.desktop create mode 100644 flatpak/app.winboat.WinBoat.metainfo.xml create mode 100644 flatpak/app.winboat.WinBoat.yml create mode 100644 flatpak/pages-index.html create mode 100644 flatpak/winboat.sh diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 54455808..2f1c1823 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,7 +1,7 @@ { $schema: "https://docs.renovatebot.com/renovate-schema.json", extends: ["config:recommended", ":disableRateLimiting"], - includePaths: ["**/src/renderer/lib/**"], + includePaths: ["**/src/renderer/lib/**", "flatpak/**"], regexManagers: [ { fileMatch: ["src/renderer/lib/install\\.ts$"], diff --git a/.github/workflows/flatpak-pages.yml b/.github/workflows/flatpak-pages.yml new file mode 100644 index 00000000..ea5d377e --- /dev/null +++ b/.github/workflows/flatpak-pages.yml @@ -0,0 +1,108 @@ +# Optional GitHub Pages site: static Flatpak repo + .flatpakrepo / .flatpakref / bundle. +# Enable Pages (branch: gh-pages) in repo Settings before relying on this workflow. +name: Publish Flatpak repository + +on: + workflow_dispatch: + push: + tags: + - "v*" + +permissions: + contents: write + +concurrency: + group: flatpak-pages-${{ github.repository }} + cancel-in-progress: true + +jobs: + pages-flatpak-repo: + runs-on: ubuntu-22.04 + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Update USB IDs database + run: | + rm -f data/usb.ids + curl -fsSL -o data/usb.ids http://www.linux-usb.org/usb.ids + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev libusb-1.0-0-dev flatpak flatpak-builder + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "stable" + cache-dependency-path: "guest_server/go.sum" + + - name: Cache Flatpak builder state + uses: actions/cache@v4 + with: + path: ~/.flatpak-builder/cache + key: flatpak-pages-${{ runner.os }}-24.08-${{ hashFiles('flatpak/app.winboat.WinBoat.yml') }} + restore-keys: | + flatpak-pages-${{ runner.os }}-24.08- + + - name: Install dependencies + run: bun ci + + - name: Build guest server and Linux unpacked tree + run: bun run build:linux-gs + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ELECTRON_BUILDER_COMPRESSION_LEVEL: 5 + + - name: Build Flatpak repo and bundle + run: | + set -eux + flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + flatpak-builder --user --disable-rofiles-fuse \ + --install-deps-from=flathub \ + --repo=fp-repo \ + --force-clean \ + fp-build-dir \ + flatpak/app.winboat.WinBoat.yml + flatpak build-update-repo fp-repo --generate-static-deltas + flatpak build-bundle --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo fp-repo winboat.flatpak app.winboat.WinBoat stable + + - name: Assemble Pages directory + run: | + set -eux + OWNER="${GITHUB_REPOSITORY%%/*}" + REPO="${GITHUB_REPOSITORY##*/}" + BASE_URL="https://${OWNER}.github.io/${REPO}" + mkdir -p site/repo + cp -a fp-repo/. site/repo/ + cp winboat.flatpak site/ + cp flatpak/pages-index.html site/index.html + { + echo '[Flatpak Repo]' + echo 'Title=WinBoat' + echo "Url=${BASE_URL}/repo/" + echo "Homepage=https://github.com/${GITHUB_REPOSITORY}" + echo 'Comment=Nightly Flatpak repository (unsigned unless you add GPG)' + echo 'Description=WinBoat — Windows apps on Linux via containers' + } > site/winboat.flatpakrepo + { + echo '[Flatpak Ref]' + echo 'Name=app.winboat.WinBoat' + echo 'Branch=stable' + echo 'Title=WinBoat' + echo "Url=${BASE_URL}/repo/" + echo 'RuntimeRepo=https://flathub.org/repo/flathub.flatpakrepo' + echo 'IsRuntime=false' + echo 'SuggestRemoteName=winboat' + } > site/winboat.flatpakref + + - name: Deploy to gh-pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./site + force_orphan: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3be813b1..38e5b021 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -167,7 +167,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libudev-dev libusb-1.0-0-dev + sudo apt-get install -y libudev-dev libusb-1.0-0-dev flatpak flatpak-builder - name: Set up Bun uses: oven-sh/setup-bun@v2 - name: Set up Go @@ -175,6 +175,13 @@ jobs: with: go-version: "stable" cache-dependency-path: "guest_server/go.sum" + - name: Cache Flatpak builder state + uses: actions/cache@v4 + with: + path: ~/.flatpak-builder/cache + key: flatpak-release-${{ runner.os }}-24.08-${{ hashFiles('flatpak/app.winboat.WinBoat.yml') }} + restore-keys: | + flatpak-release-${{ runner.os }}-24.08- - name: Install dependencies run: bun ci - name: Build guest server and app @@ -182,6 +189,17 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ELECTRON_BUILDER_COMPRESSION_LEVEL: 5 + - name: Build Flatpak bundle + run: | + set -eux + flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + flatpak-builder --user --disable-rofiles-fuse \ + --install-deps-from=flathub \ + --repo=flatpak-repo-ci \ + --force-clean \ + flatpak-build-dir \ + flatpak/app.winboat.WinBoat.yml + flatpak build-bundle --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo flatpak-repo-ci dist/winboat.flatpak app.winboat.WinBoat stable - name: Zip unpacked variant run: cd dist && zip -r winboat-linux-unpacked.zip linux-unpacked/ - name: Create GitHub Release @@ -192,6 +210,7 @@ jobs: dist/*.deb dist/*.rpm dist/*.tar.bz2 + dist/winboat.flatpak dist/winboat-linux-unpacked.zip dist/latest-linux.yml dist/linux-unpacked/resources/guest_server/winboat_guest_server.zip diff --git a/README.md b/README.md index d21ea06d..64db031e 100644 --- a/README.md +++ b/README.md @@ -67,12 +67,26 @@ Before running WinBoat, ensure your system meets the following requirements: ## Downloading -You can download the latest Linux builds under the [Releases](https://github.com/TibixDev/winboat/releases) tab. We currently offer four variants: +You can download the latest Linux builds under the [Releases](https://github.com/TibixDev/winboat/releases) tab. We currently offer these variants: - **AppImage:** A popular & portable app format which should run fine on most distributions +- **Flatpak (`winboat.flatpak`):** Single-file bundle from releases (`flatpak install --bundle ./winboat.flatpak`). Uses Flatpak app ID `app.winboat.WinBoat` (for stores like Flathub); your Electron/desktop integration ID remains `com.teabox.winboat`. When you bump `package.json` **version**, update `` in [`flatpak/app.winboat.WinBoat.metainfo.xml`](flatpak/app.winboat.WinBoat.metainfo.xml) for accurate store metadata. - **Unpacked:** The raw unpacked files, simply run the executable (`linux-unpacked/winboat`) - **.deb:** The intended format for Debian based distributions - **.rpm:** The intended format for Fedora based distributions + +### Flatpak details + +WinBoat drives **Docker or Podman on the host** and uses **KVM** (`/dev/kvm`) for the Windows VM inside [dockur/windows](https://github.com/dockur/windows). The Flatpak is wired for that: host home directory access, container sockets (`/run/docker.sock`, XDG Podman paths), DRI, Pulse/PipeWire audio, and talking to the host Flatpak session so `flatpak run com.freerdp.FreeRDP` can satisfy FreeRDP 3. + +**Flathub:** Publishing on [Flathub](https://flathub.org/) is a separate submission ([author docs](https://docs.flathub.org/docs/for-app-authors/submission)). Reviewers treat apps that depend heavily on host services case-by-case; upstream maintenance expectation applies especially where emulation or host tooling is involved. The canonical manifest for packaging lives at [`flatpak/app.winboat.WinBoat.yml`](flatpak/app.winboat.WinBoat.yml). + +**Optional GitHub Pages repo:** The workflow [flatpak-pages.yml](.github/workflows/flatpak-pages.yml) builds an OSTree repo plus `.flatpakrepo`, `.flatpakref`, and a bundle on tags (`v*`) or manual dispatch, and pushes them to the `gh-pages` branch (enable **Pages** in the repo settings first). To mirror Flathub’s builder locally: + +```bash +docker run --rm -it -v "$PWD:/work" -w /work ghcr.io/flathub-infra/flatpak-builder:stable \ + flatpak-builder --help +``` - **Nix (Nixpkgs)** 1. Add the winboat package to your config (ensure using nixpkgs-unstable) using `environment.systemPackages = [pkgs.winboat];` or `home.packages = [pkgs.winboat];` if using home manager. diff --git a/flatpak/app.winboat.WinBoat.desktop b/flatpak/app.winboat.WinBoat.desktop new file mode 100644 index 00000000..eee998f3 --- /dev/null +++ b/flatpak/app.winboat.WinBoat.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Version=1.5 +Name=WinBoat +Comment=Run Windows apps on Linux with Docker or Podman +Exec=winboat %u +Icon=app.winboat.WinBoat +Categories=Utility;System; +Terminal=false +StartupNotify=true diff --git a/flatpak/app.winboat.WinBoat.metainfo.xml b/flatpak/app.winboat.WinBoat.metainfo.xml new file mode 100644 index 00000000..6b0b1f36 --- /dev/null +++ b/flatpak/app.winboat.WinBoat.metainfo.xml @@ -0,0 +1,43 @@ + + + app.winboat.WinBoat + MIT + MIT + WinBoat + Windows for Penguins — Windows apps on Linux via containers + +

+ WinBoat is an Electron desktop app that runs Windows inside a Docker or Podman container (QEMU/KVM), + then integrates RemoteApp windows into your Linux session using FreeRDP 3.x. +

+

+ You must install and run Docker Engine or Podman on the host, enable KVM (/dev/kvm), and install FreeRDP 3 + (host package or the Flathub FreeRDP app). The Flatpak can talk to the host container socket and KVM device. +

+
+ app.winboat.WinBoat.desktop + https://www.winboat.app/ + https://github.com/TibixDev/winboat/issues + https://github.com/TibixDev/winboat + + + Dashboard + https://raw.githubusercontent.com/TibixDev/winboat/main/gh-assets/features/feat_dash.png + + + Apps + https://raw.githubusercontent.com/TibixDev/winboat/main/gh-assets/features/feat_apps.png + + + Native windows + https://raw.githubusercontent.com/TibixDev/winboat/main/gh-assets/features/feat_native.png + + + + + + + + TibixDev + +
diff --git a/flatpak/app.winboat.WinBoat.yml b/flatpak/app.winboat.WinBoat.yml new file mode 100644 index 00000000..49b6af5b --- /dev/null +++ b/flatpak/app.winboat.WinBoat.yml @@ -0,0 +1,50 @@ +app-id: app.winboat.WinBoat +runtime: org.freedesktop.Platform +runtime-version: '24.08' +sdk: org.freedesktop.Sdk +command: winboat + +finish-args: + - --share=network + - --share=ipc + - --socket=fallback-x11 + - --socket=wayland + - --socket=pulseaudio + - --device=dri + - --device=kvm + - --filesystem=home + - --filesystem=/run/docker.sock + - --filesystem=xdg-run/docker + - --filesystem=xdg-run/podman:create + - --filesystem=xdg-run/podman + - --talk-name=org.freedesktop.Flatpak + +modules: + - name: winboat + buildsystem: simple + build-commands: + - cp -a . "${FLATPAK_DEST}/winboat/" + - rm -f "${FLATPAK_DEST}/winboat/winboat.sh" + - rm -f "${FLATPAK_DEST}/winboat/app.winboat.WinBoat.desktop" + - rm -f "${FLATPAK_DEST}/winboat/app.winboat.WinBoat.metainfo.xml" + - rm -f "${FLATPAK_DEST}/winboat/winboat_logo.svg" + - | + if [ -x "${FLATPAK_DEST}/winboat/WinBoat" ] && [ ! -e "${FLATPAK_DEST}/winboat/winboat" ]; then + ln -s WinBoat "${FLATPAK_DEST}/winboat/winboat" + fi + - chmod +x "${FLATPAK_DEST}/winboat/winboat" + - install -Dm755 winboat.sh "${FLATPAK_DEST}/bin/winboat" + - install -Dm644 app.winboat.WinBoat.desktop "${FLATPAK_DEST}/share/applications/app.winboat.WinBoat.desktop" + - install -Dm644 app.winboat.WinBoat.metainfo.xml "${FLATPAK_DEST}/share/metainfo/app.winboat.WinBoat.metainfo.xml" + - install -Dm644 winboat_logo.svg "${FLATPAK_DEST}/share/icons/hicolor/scalable/apps/app.winboat.WinBoat.svg" + sources: + - type: dir + path: ../dist/linux-unpacked + - type: file + path: winboat.sh + - type: file + path: app.winboat.WinBoat.desktop + - type: file + path: app.winboat.WinBoat.metainfo.xml + - type: file + path: ../icons/winboat_logo.svg diff --git a/flatpak/pages-index.html b/flatpak/pages-index.html new file mode 100644 index 00000000..0bd71175 --- /dev/null +++ b/flatpak/pages-index.html @@ -0,0 +1,28 @@ + + + + + + WinBoat — Flatpak + + + +

WinBoat Flatpak

+

These CI artifacts are for testing; an official Flathub listing is tracked upstream separately.

+

Requirements: Docker or Podman on the host, KVM, and FreeRDP 3 (host install or com.freerdp.FreeRDP from Flathub).

+

Install

+

Add repository + One-click ref + Download bundle

+

If the repo is unsigned, use flatpak remote-add --user --no-gpg-verify winboat <Repo URL from .flatpakrepo>.

+

CLI

+
# Use this site’s origin as BASE (e.g. https://you.github.io/winboat)
+flatpak remote-add --user --no-gpg-verify winboat BASE/winboat.flatpakrepo
+flatpak install winboat app.winboat.WinBoat
+ + diff --git a/flatpak/winboat.sh b/flatpak/winboat.sh new file mode 100644 index 00000000..cc92b15f --- /dev/null +++ b/flatpak/winboat.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu +export TMPDIR="${XDG_RUNTIME_DIR:-/tmp}/app.${FLATPAK_ID:-app.winboat.WinBoat}" +mkdir -p "${TMPDIR}" +if [ -x /app/winboat/winboat ]; then exec /app/winboat/winboat "${@}"; fi +if [ -x /app/winboat/WinBoat ]; then exec /app/winboat/WinBoat "${@}"; fi +echo "WinBoat executable not found under /app/winboat" >&2 +exit 1 From 4d742d9e5f883fe5333c2fedeb367d870601f30a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 2 May 2026 06:32:37 -0800 Subject: [PATCH 2/5] deploying --- .github/workflows/flatpak-pages.yml | 4 ++-- .github/workflows/release.yml | 30 ++++++++++++++++++++++++++--- README.md | 22 +++++++++++---------- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/.github/workflows/flatpak-pages.yml b/.github/workflows/flatpak-pages.yml index ea5d377e..f3a8d542 100644 --- a/.github/workflows/flatpak-pages.yml +++ b/.github/workflows/flatpak-pages.yml @@ -45,9 +45,9 @@ jobs: uses: actions/cache@v4 with: path: ~/.flatpak-builder/cache - key: flatpak-pages-${{ runner.os }}-24.08-${{ hashFiles('flatpak/app.winboat.WinBoat.yml') }} + key: flatpak-${{ runner.os }}-24.08-${{ hashFiles('flatpak/app.winboat.WinBoat.yml') }} restore-keys: | - flatpak-pages-${{ runner.os }}-24.08- + flatpak-${{ runner.os }}-24.08- - name: Install dependencies run: bun ci diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 38e5b021..1ab640a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libudev-dev libusb-1.0-0-dev + sudo apt-get install -y libudev-dev libusb-1.0-0-dev flatpak flatpak-builder - name: Set up Bun uses: oven-sh/setup-bun@v2 - name: Set up Go @@ -52,6 +52,13 @@ jobs: with: go-version: "stable" cache-dependency-path: "guest_server/go.sum" + - name: Cache Flatpak builder state + uses: actions/cache@v4 + with: + path: ~/.flatpak-builder/cache + key: flatpak-${{ runner.os }}-24.08-${{ hashFiles('flatpak/app.winboat.WinBoat.yml') }} + restore-keys: | + flatpak-${{ runner.os }}-24.08- - name: Install dependencies run: bun ci - name: Build guest server and app @@ -77,6 +84,23 @@ jobs: *) echo "ARCH=$BUN_ARCH" >> "$GITHUB_OUTPUT" ;; esac fi + - name: Build Flatpak bundle + run: | + set -eux + flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + flatpak-builder --user --disable-rofiles-fuse \ + --install-deps-from=flathub \ + --repo=flatpak-repo-ci \ + --force-clean \ + flatpak-build-dir \ + flatpak/app.winboat.WinBoat.yml + flatpak build-bundle --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo flatpak-repo-ci dist/winboat.flatpak app.winboat.WinBoat stable + - name: Upload Flatpak bundle + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.meta.outputs.NAME }}-${{ steps.meta.outputs.VERSION }}-${{ steps.meta.outputs.ARCH }}.flatpak + path: dist/winboat.flatpak + if-no-files-found: error - name: Upload AppImage uses: actions/upload-artifact@v4 with: @@ -179,9 +203,9 @@ jobs: uses: actions/cache@v4 with: path: ~/.flatpak-builder/cache - key: flatpak-release-${{ runner.os }}-24.08-${{ hashFiles('flatpak/app.winboat.WinBoat.yml') }} + key: flatpak-${{ runner.os }}-24.08-${{ hashFiles('flatpak/app.winboat.WinBoat.yml') }} restore-keys: | - flatpak-release-${{ runner.os }}-24.08- + flatpak-${{ runner.os }}-24.08- - name: Install dependencies run: bun ci - name: Build guest server and app diff --git a/README.md b/README.md index 64db031e..137df6fc 100644 --- a/README.md +++ b/README.md @@ -67,13 +67,23 @@ Before running WinBoat, ensure your system meets the following requirements: ## Downloading -You can download the latest Linux builds under the [Releases](https://github.com/TibixDev/winboat/releases) tab. We currently offer these variants: +You can download the latest Linux builds from **[Releases](https://github.com/TibixDev/winboat/releases)** (created when you push a **`v*`** tag or run **Actions → Build WinBoat → Run workflow** with **Build type: all**) and from **every push to `main`** via **Actions → latest run → Artifacts** (AppImage, `.flatpak`, etc.). + +We currently offer these variants: - **AppImage:** A popular & portable app format which should run fine on most distributions -- **Flatpak (`winboat.flatpak`):** Single-file bundle from releases (`flatpak install --bundle ./winboat.flatpak`). Uses Flatpak app ID `app.winboat.WinBoat` (for stores like Flathub); your Electron/desktop integration ID remains `com.teabox.winboat`. When you bump `package.json` **version**, update `` in [`flatpak/app.winboat.WinBoat.metainfo.xml`](flatpak/app.winboat.WinBoat.metainfo.xml) for accurate store metadata. +- **Flatpak (`winboat.flatpak`):** After each successful **`build`** workflow on `main`, download the artifact named like `winboat-*-x86_64.flatpak`, extract it if zipped (GitHub zips single-file artifacts), then run `flatpak install --bundle ./winboat.flatpak`. The same file is attached to **Releases** when a release is produced. Uses Flatpak app ID `app.winboat.WinBoat` (for stores like Flathub); your Electron/desktop integration ID remains `com.teabox.winboat`. When you bump `package.json` **version**, update `` in [`flatpak/app.winboat.WinBoat.metainfo.xml`](flatpak/app.winboat.WinBoat.metainfo.xml) for accurate store metadata. - **Unpacked:** The raw unpacked files, simply run the executable (`linux-unpacked/winboat`) - **.deb:** The intended format for Debian based distributions - **.rpm:** The intended format for Fedora based distributions +- **Nix (Nixpkgs)** + 1. Add the winboat package to your config (ensure using nixpkgs-unstable) + using `environment.systemPackages = [pkgs.winboat];` or `home.packages = [pkgs.winboat];` if using home manager. + 2. Add the following lines to your nix configuration + ```nix + virtualisation.docker.enable = true; + users.users.{yourUser}.extraGroups = ["docker"]; + ``` ### Flatpak details @@ -87,14 +97,6 @@ WinBoat drives **Docker or Podman on the host** and uses **KVM** (`/dev/kvm`) fo docker run --rm -it -v "$PWD:/work" -w /work ghcr.io/flathub-infra/flatpak-builder:stable \ flatpak-builder --help ``` -- **Nix (Nixpkgs)** - 1. Add the winboat package to your config (ensure using nixpkgs-unstable) - using `environment.systemPackages = [pkgs.winboat];` or `home.packages = [pkgs.winboat];` if using home manager. - 2. Add the following lines to your nix configuration - ```nix - virtualisation.docker.enable = true; - users.users.{yourUser}.extraGroups = ["docker"]; - ``` ## Known Issues About Container Runtimes - Docker Desktop is **unsupported** for now From 80fee78f485cb0cbfbe84e191a4e3024ca0c5b0f Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 2 May 2026 07:46:45 -0800 Subject: [PATCH 3/5] local build pass --- .github/workflows/flatpak-pages.yml | 24 ++++------- .github/workflows/release.yml | 34 +++++---------- .gitignore | 8 +++- README.md | 55 ++++++++++++++++++++++-- bun.lock | 5 ++- electron-builder.json | 4 +- flatpak/app.winboat.WinBoat.desktop | 0 flatpak/app.winboat.WinBoat.metainfo.xml | 0 flatpak/app.winboat.WinBoat.yml | 4 ++ flatpak/build-bundle.sh | 54 +++++++++++++++++++++++ flatpak/pages-index.html | 0 flatpak/winboat.sh | 0 package.json | 10 ++++- scripts/build.ts | 31 +++++++++++-- tailwind.config.ts | 2 +- 15 files changed, 178 insertions(+), 53 deletions(-) mode change 100644 => 100755 flatpak/app.winboat.WinBoat.desktop mode change 100644 => 100755 flatpak/app.winboat.WinBoat.metainfo.xml mode change 100644 => 100755 flatpak/app.winboat.WinBoat.yml create mode 100755 flatpak/build-bundle.sh mode change 100644 => 100755 flatpak/pages-index.html mode change 100644 => 100755 flatpak/winboat.sh diff --git a/.github/workflows/flatpak-pages.yml b/.github/workflows/flatpak-pages.yml index f3a8d542..d0b13bdb 100644 --- a/.github/workflows/flatpak-pages.yml +++ b/.github/workflows/flatpak-pages.yml @@ -44,7 +44,9 @@ jobs: - name: Cache Flatpak builder state uses: actions/cache@v4 with: - path: ~/.flatpak-builder/cache + path: | + ~/.flatpak-builder/cache + flatpak/.flatpak-builder-state key: flatpak-${{ runner.os }}-24.08-${{ hashFiles('flatpak/app.winboat.WinBoat.yml') }} restore-keys: | flatpak-${{ runner.os }}-24.08- @@ -52,24 +54,14 @@ jobs: - name: Install dependencies run: bun ci - - name: Build guest server and Linux unpacked tree - run: bun run build:linux-gs + - name: Build Linux unpacked (dir target; no RPM) + run: bun run build:linux-dir env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ELECTRON_BUILDER_COMPRESSION_LEVEL: 5 - name: Build Flatpak repo and bundle - run: | - set -eux - flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak-builder --user --disable-rofiles-fuse \ - --install-deps-from=flathub \ - --repo=fp-repo \ - --force-clean \ - fp-build-dir \ - flatpak/app.winboat.WinBoat.yml - flatpak build-update-repo fp-repo --generate-static-deltas - flatpak build-bundle --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo fp-repo winboat.flatpak app.winboat.WinBoat stable + run: bun run build:flatpak - name: Assemble Pages directory run: | @@ -78,8 +70,8 @@ jobs: REPO="${GITHUB_REPOSITORY##*/}" BASE_URL="https://${OWNER}.github.io/${REPO}" mkdir -p site/repo - cp -a fp-repo/. site/repo/ - cp winboat.flatpak site/ + cp -a flatpak/.flatpak-repo-local/. site/repo/ + cp dist/winboat.flatpak site/ cp flatpak/pages-index.html site/index.html { echo '[Flatpak Repo]' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1ab640a7..7dd96b9f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libudev-dev libusb-1.0-0-dev flatpak flatpak-builder + sudo apt-get install -y libudev-dev libusb-1.0-0-dev flatpak flatpak-builder rpm - name: Set up Bun uses: oven-sh/setup-bun@v2 - name: Set up Go @@ -55,7 +55,9 @@ jobs: - name: Cache Flatpak builder state uses: actions/cache@v4 with: - path: ~/.flatpak-builder/cache + path: | + ~/.flatpak-builder/cache + flatpak/.flatpak-builder-state key: flatpak-${{ runner.os }}-24.08-${{ hashFiles('flatpak/app.winboat.WinBoat.yml') }} restore-keys: | flatpak-${{ runner.os }}-24.08- @@ -85,16 +87,7 @@ jobs: esac fi - name: Build Flatpak bundle - run: | - set -eux - flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak-builder --user --disable-rofiles-fuse \ - --install-deps-from=flathub \ - --repo=flatpak-repo-ci \ - --force-clean \ - flatpak-build-dir \ - flatpak/app.winboat.WinBoat.yml - flatpak build-bundle --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo flatpak-repo-ci dist/winboat.flatpak app.winboat.WinBoat stable + run: bun run build:flatpak - name: Upload Flatpak bundle uses: actions/upload-artifact@v4 with: @@ -191,7 +184,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libudev-dev libusb-1.0-0-dev flatpak flatpak-builder + sudo apt-get install -y libudev-dev libusb-1.0-0-dev flatpak flatpak-builder rpm - name: Set up Bun uses: oven-sh/setup-bun@v2 - name: Set up Go @@ -202,7 +195,9 @@ jobs: - name: Cache Flatpak builder state uses: actions/cache@v4 with: - path: ~/.flatpak-builder/cache + path: | + ~/.flatpak-builder/cache + flatpak/.flatpak-builder-state key: flatpak-${{ runner.os }}-24.08-${{ hashFiles('flatpak/app.winboat.WinBoat.yml') }} restore-keys: | flatpak-${{ runner.os }}-24.08- @@ -214,16 +209,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ELECTRON_BUILDER_COMPRESSION_LEVEL: 5 - name: Build Flatpak bundle - run: | - set -eux - flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak-builder --user --disable-rofiles-fuse \ - --install-deps-from=flathub \ - --repo=flatpak-repo-ci \ - --force-clean \ - flatpak-build-dir \ - flatpak/app.winboat.WinBoat.yml - flatpak build-bundle --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo flatpak-repo-ci dist/winboat.flatpak app.winboat.WinBoat stable + run: bun run build:flatpak - name: Zip unpacked variant run: cd dist && zip -r winboat-linux-unpacked.zip linux-unpacked/ - name: Create GitHub Release diff --git a/.gitignore b/.gitignore index 784b4536..605b0491 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,10 @@ package-lock.json # *.exe temp guest_server/winboat_guest_server.exe -guest_server/winboat_guest_server.zip \ No newline at end of file +guest_server/winboat_guest_server.zip + +# Local Flatpak builder outputs +flatpak/.flatpak-build-dir +flatpak/.flatpak-repo-local +flatpak/.flatpak-builder-state +.flatpak-builder \ No newline at end of file diff --git a/README.md b/README.md index 137df6fc..cd667930 100644 --- a/README.md +++ b/README.md @@ -104,11 +104,60 @@ docker run --rm -it -v "$PWD:/work" -w /work ghcr.io/flathub-infra/flatpak-build ## Building WinBoat -- For building you need to have Bun and Go installed on your system +- For building you need **Bun**, **Go**, and **Node.js** (see `engines` in `package.json`; Vite’s CLI runs under `node` during production builds) - Clone the repo (`git clone https://github.com/TibixDev/WinBoat`) - Install the dependencies (`bun i`) -- Build the app and the guest server using `bun run build:linux-gs` -- You can now find the built app under `dist` with an AppImage and an Unpacked variant + +**Linux outputs:** + +| Command | Produces | Notes | +|--------|----------|--------| +| `bun run build:linux-dir` | `dist/linux-unpacked/` only | No AppImage/deb/rpm; **no `rpmbuild`** needed — use this before Flatpak locally. | +| `bun run build:linux-gs` | AppImage, deb, **rpm**, tar.bz2, unpacked | On Debian/Ubuntu install **`rpm`** so electron-builder can call `rpmbuild`: `sudo apt install rpm`. | + +CI installs **`rpm`** alongside **`flatpak`** / **`flatpak-builder`** so release jobs match your `electron-builder.json` targets. + +### Building the Flatpak bundle locally + +You need **`flatpak`** and **`flatpak-builder`** (e.g. `sudo apt install flatpak flatpak-builder`). The first run downloads the Freedesktop SDK/runtime from Flathub and can take a while. + +The source build expects **`Node.js` on your `PATH`** so Vite runs under Node; Bun runs scripts and installs deps. If you see `Cannot find module 'vite/module-runner'`, run `rm -rf node_modules && bun install` so **Vite stays at 7.3.1** (pinned in `package.json` + `overrides`). + +**Recommended one-shot** (unpack dir target + bundle — avoids RPM tooling): + +```bash +bun run build:linux-flatpak +flatpak install --bundle ./dist/winboat.flatpak +``` + +If you already built unpacked output (`build:linux-dir` or `build:linux-gs`): + +```bash +bun run build:flatpak +``` + +Gitignored Flatpak working dirs: `flatpak/.flatpak-build-dir`, `flatpak/.flatpak-repo-local`, `flatpak/.flatpak-builder-state`, and repo-root **`.flatpak-builder`** (cache flatpak-builder sometimes writes next to the project). **`electron-builder` excludes `flatpak/` and `.flatpak-builder/`** so it does not traverse sandbox or cache trees (`EACCES` under paths like `wpa_supplicant`). If problems persist, delete those directories — avoid `chmod -R` on them. + +### Bun install and Docker + +- **Host install:** `curl -fsSL https://bun.sh/install | bash` then restart your shell (or `source ~/.bashrc`). +- **`oven/bun`** images ship Bun only — they do **not** include Flatpak, `rpmbuild`, or your distro libraries. Use them only for steps like `bun ci` / `bun run build:linux-dir` **inside** a volume-mounted repo; run **`flatpak/build-bundle.sh`** on the host (or use a **full OS container** below). + +**Full Linux build in Docker (example):** Debian/Ubuntu base + deps + Bun + Node + Go + Flatpak tooling + `rpm` for parity with CI: + +```bash +docker run --rm -it \ + -v "$(pwd):/src" -w /src \ + ubuntu:22.04 bash -lc ' + apt-get update && apt-get install -y curl ca-certificates git unzip \ + libudev-dev libusb-1.0-0-dev flatpak flatpak-builder rpm golang-go nodejs npm && + curl -fsSL https://bun.sh/install | bash && + export PATH="$HOME/.bun/bin:$PATH" && + bun ci && bun run build:linux-flatpak + ' +``` + +Use a **newer Node** than the default `nodejs` package if your distro’s version is below `engines.node` (e.g. install from [nodejs.org](https://nodejs.org/) inside the image). ## Running WinBoat in development mode diff --git a/bun.lock b/bun.lock index 9338521f..a165fec7 100644 --- a/bun.lock +++ b/bun.lock @@ -41,11 +41,14 @@ "prettier": "3.8.1", "tailwindcss": "^3.4.17", "typescript": "~5.9.3", - "vite": "^7.3.1", + "vite": "7.3.1", "vue-tsc": "^3.2.4", }, }, }, + "overrides": { + "vite": "7.3.1", + }, "packages": { "7zip-bin": ["7zip-bin@5.2.0", "", {}, "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A=="], diff --git a/electron-builder.json b/electron-builder.json index 7a6a5524..981dc1d0 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -38,7 +38,9 @@ }, "!build", "!dist", - "!scripts" + "!scripts", + "!flatpak", + "!.flatpak-builder" ], "extraResources": [ { diff --git a/flatpak/app.winboat.WinBoat.desktop b/flatpak/app.winboat.WinBoat.desktop old mode 100644 new mode 100755 diff --git a/flatpak/app.winboat.WinBoat.metainfo.xml b/flatpak/app.winboat.WinBoat.metainfo.xml old mode 100644 new mode 100755 diff --git a/flatpak/app.winboat.WinBoat.yml b/flatpak/app.winboat.WinBoat.yml old mode 100644 new mode 100755 index 49b6af5b..1f0434fc --- a/flatpak/app.winboat.WinBoat.yml +++ b/flatpak/app.winboat.WinBoat.yml @@ -1,9 +1,13 @@ app-id: app.winboat.WinBoat +branch: stable runtime: org.freedesktop.Platform runtime-version: '24.08' sdk: org.freedesktop.Sdk command: winboat +build-options: + no-debuginfo: true + finish-args: - --share=network - --share=ipc diff --git a/flatpak/build-bundle.sh b/flatpak/build-bundle.sh new file mode 100755 index 00000000..0d495bb5 --- /dev/null +++ b/flatpak/build-bundle.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Build dist/winboat.flatpak from dist/linux-unpacked. +# Run after: bun run build:linux-dir (Flatpak-friendly; no rpmbuild) +# or bun run build:linux-gs (full AppImage/deb/rpm/… — CI installs rpm). +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${ROOT}" + +for cmd in flatpak flatpak-builder; do + if ! command -v "${cmd}" >/dev/null 2>&1; then + echo "Missing '${cmd}'. Install it (e.g. sudo apt install flatpak flatpak-builder) and try again." >&2 + exit 1 + fi +done + +if [ ! -d dist/linux-unpacked ]; then + echo "dist/linux-unpacked not found. Run first:" >&2 + echo " bun run build:linux-dir # recommended for Flatpak (no RPM tooling)" >&2 + echo " bun run build:linux-gs # full Linux targets (needs rpmbuild for .rpm)" >&2 + exit 1 +fi + +flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + +BUILD_DIR="${ROOT}/flatpak/.flatpak-build-dir" +REPO_DIR="${ROOT}/flatpak/.flatpak-repo-local" +STATE_DIR="${ROOT}/flatpak/.flatpak-builder-state" +mkdir -p "${BUILD_DIR}" "${REPO_DIR}" "${STATE_DIR}" + +MANIFEST="${ROOT}/flatpak/app.winboat.WinBoat.yml" + +flatpak-builder --user --disable-rofiles-fuse \ + --state-dir="${STATE_DIR}" \ + --default-branch=stable \ + --install-deps-from=flathub \ + --repo="${REPO_DIR}" \ + --force-clean \ + "${BUILD_DIR}" \ + "${MANIFEST}" + +flatpak build-update-repo "${REPO_DIR}" --generate-static-deltas + +mkdir -p dist +flatpak build-bundle \ + --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo \ + "${REPO_DIR}" \ + "${ROOT}/dist/winboat.flatpak" \ + app.winboat.WinBoat \ + stable + +echo "Built ${ROOT}/dist/winboat.flatpak" +echo "OSTree repo (for Pages / mirrors): ${REPO_DIR}" +echo "Install with: flatpak install --bundle ./dist/winboat.flatpak" diff --git a/flatpak/pages-index.html b/flatpak/pages-index.html old mode 100644 new mode 100755 diff --git a/flatpak/winboat.sh b/flatpak/winboat.sh old mode 100644 new mode 100755 diff --git a/package.json b/package.json index 4c08c4bc..fa137c65 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,16 @@ "node": ">=23.6.0", "bun": ">=1.3.0" }, + "overrides": { + "vite": "7.3.1" + }, "scripts": { "dev": "bun scripts/dev-server.ts", "build:gs": "bash build-guest-server.sh", - "build:linux-gs": "bash build-guest-server.sh && bun scripts/build.ts && electron-builder --linux" + "build:linux-dir": "bash build-guest-server.sh && bun scripts/build.ts && electron-builder --linux dir", + "build:linux-gs": "bash build-guest-server.sh && bun scripts/build.ts && electron-builder --linux", + "build:flatpak": "bash flatpak/build-bundle.sh", + "build:linux-flatpak": "bun run build:linux-dir && bun run build:flatpak" }, "repository": "https://github.com/TibixDev/winboat", "author": { @@ -32,7 +38,7 @@ "prettier": "3.8.1", "tailwindcss": "^3.4.17", "typescript": "~5.9.3", - "vite": "^7.3.1", + "vite": "7.3.1", "vue-tsc": "^3.2.4" }, "dependencies": { diff --git a/scripts/build.ts b/scripts/build.ts index 77e5f1e7..2b5e42fe 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,17 +1,40 @@ +import ChildProcess from "child_process"; import Path from "path"; import Chalk from "chalk"; import FileSystem from "fs"; -import * as Vite from "vite"; import compileTs from "./private/tsc.ts"; // ^ Extension can't be omitted because Node expects it import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = Path.dirname(__filename); +const repoRoot = Path.join(__dirname, ".."); -function buildRenderer() { - return Vite.build({ - configFile: Path.join(__dirname, "..", "vite.config.ts"), +/** + * Run Vite under Node when available (avoids Bun + Vite 8+ resolution issues like missing vite/module-runner). + * Fall back to programmatic build under Bun — keep vite pinned to 7.3.x in package.json for that path. + */ +async function buildRenderer(): Promise { + const viteJs = Path.join(repoRoot, "node_modules", "vite", "bin", "vite.js"); + const config = Path.join(repoRoot, "vite.config.ts"); + const args = [viteJs, "build", "--config", config, "--base", "./", "--mode", "production"]; + + const nodeProbe = ChildProcess.spawnSync("node", ["-v"], { encoding: "utf8" }); + if (nodeProbe.status === 0) { + const r = ChildProcess.spawnSync("node", args, { + cwd: repoRoot, + stdio: "inherit", + encoding: "utf8", + }); + if (r.status !== 0) { + throw new Error("Vite build failed under Node"); + } + return; + } + + const Vite = await import("vite"); + await Vite.build({ + configFile: config, base: "./", mode: "production", }); diff --git a/tailwind.config.ts b/tailwind.config.ts index 4fe5115f..d64c3541 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,7 +1,7 @@ /** @type {import('tailwindcss').Config} */ export default { content: ["./src/renderer/index.html", "./src/renderer/**/*.{vue,js,ts,jsx,tsx}"], - darkMode: false, // or 'media' or 'class' + darkMode: "media", theme: { extend: {}, }, From bb7afbdc3aca8bc2653c179a2e7ad68fa9d61eb3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 2 May 2026 08:05:51 -0800 Subject: [PATCH 4/5] still missing few dep in sandbox --- README.md | 2 ++ flatpak/winboat.sh | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cd667930..c67ab2e7 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,8 @@ We currently offer these variants: WinBoat drives **Docker or Podman on the host** and uses **KVM** (`/dev/kvm`) for the Windows VM inside [dockur/windows](https://github.com/dockur/windows). The Flatpak is wired for that: host home directory access, container sockets (`/run/docker.sock`, XDG Podman paths), DRI, Pulse/PipeWire audio, and talking to the host Flatpak session so `flatpak run com.freerdp.FreeRDP` can satisfy FreeRDP 3. +The Flatpak wrapper runs Electron with **`--no-sandbox`** / **`--disable-setuid-sandbox`**: Chromium’s setuid `chrome-sandbox` cannot be root-owned inside a Flatpak, so process isolation for the UI relies on **Flatpak’s sandbox** instead (same pattern as many other Electron Flatpaks). + **Flathub:** Publishing on [Flathub](https://flathub.org/) is a separate submission ([author docs](https://docs.flathub.org/docs/for-app-authors/submission)). Reviewers treat apps that depend heavily on host services case-by-case; upstream maintenance expectation applies especially where emulation or host tooling is involved. The canonical manifest for packaging lives at [`flatpak/app.winboat.WinBoat.yml`](flatpak/app.winboat.WinBoat.yml). **Optional GitHub Pages repo:** The workflow [flatpak-pages.yml](.github/workflows/flatpak-pages.yml) builds an OSTree repo plus `.flatpakrepo`, `.flatpakref`, and a bundle on tags (`v*`) or manual dispatch, and pushes them to the `gh-pages` branch (enable **Pages** in the repo settings first). To mirror Flathub’s builder locally: diff --git a/flatpak/winboat.sh b/flatpak/winboat.sh index cc92b15f..21e45c87 100755 --- a/flatpak/winboat.sh +++ b/flatpak/winboat.sh @@ -2,7 +2,9 @@ set -eu export TMPDIR="${XDG_RUNTIME_DIR:-/tmp}/app.${FLATPAK_ID:-app.winboat.WinBoat}" mkdir -p "${TMPDIR}" -if [ -x /app/winboat/winboat ]; then exec /app/winboat/winboat "${@}"; fi -if [ -x /app/winboat/WinBoat ]; then exec /app/winboat/WinBoat "${@}"; fi +# Electron's setuid chrome-sandbox cannot be root-owned inside Flatpak; rely on Flatpak's sandbox instead. +SANDBOX_FLAGS="--no-sandbox --disable-setuid-sandbox" +if [ -x /app/winboat/winboat ]; then exec /app/winboat/winboat ${SANDBOX_FLAGS} "$@"; fi +if [ -x /app/winboat/WinBoat ]; then exec /app/winboat/WinBoat ${SANDBOX_FLAGS} "$@"; fi echo "WinBoat executable not found under /app/winboat" >&2 exit 1 From e78df6c4be1746dd59569e6415963fe6f5bb8226 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 2 May 2026 08:24:33 -0800 Subject: [PATCH 5/5] host dependencies working (need host docker ...) --- README.md | 20 +++++++++++-- flatpak/app.winboat.WinBoat.yml | 5 ++++ flatpak/winboat.sh | 5 +++- src/renderer/lib/containers/docker.ts | 43 ++++++++++++++++++--------- src/renderer/lib/containers/podman.ts | 35 +++++++++++++++------- src/renderer/lib/flatpak-host.ts | 17 +++++++++++ src/renderer/lib/winboat.ts | 8 +++-- src/renderer/utils/getFreeRDP.ts | 13 +++++++- 8 files changed, 115 insertions(+), 31 deletions(-) create mode 100644 src/renderer/lib/flatpak-host.ts diff --git a/README.md b/README.md index c67ab2e7..8f30bb32 100644 --- a/README.md +++ b/README.md @@ -87,9 +87,25 @@ We currently offer these variants: ### Flatpak details -WinBoat drives **Docker or Podman on the host** and uses **KVM** (`/dev/kvm`) for the Windows VM inside [dockur/windows](https://github.com/dockur/windows). The Flatpak is wired for that: host home directory access, container sockets (`/run/docker.sock`, XDG Podman paths), DRI, Pulse/PipeWire audio, and talking to the host Flatpak session so `flatpak run com.freerdp.FreeRDP` can satisfy FreeRDP 3. +WinBoat drives **Docker or Podman on the host** and uses **KVM** (`/dev/kvm`) for the Windows VM inside [dockur/windows](https://github.com/dockur/windows). The Flatpak is wired for that: host home directory access, container sockets (`/run/docker.sock`, XDG Podman paths), DRI, Pulse/PipeWire audio, **session + system D-Bus** (so Chromium/Electron stops probing a missing `/run/dbus/system_bus_socket`), **`ELECTRON_TRASH=gio`**, host icon paths for cursor scaling under Wayland, and talking to the host Flatpak session so `flatpak run com.freerdp.FreeRDP` can satisfy FreeRDP 3. -The Flatpak wrapper runs Electron with **`--no-sandbox`** / **`--disable-setuid-sandbox`**: Chromium’s setuid `chrome-sandbox` cannot be root-owned inside a Flatpak, so process isolation for the UI relies on **Flatpak’s sandbox** instead (same pattern as many other Electron Flatpaks). +Packaging follows ideas from the official [Electron on Flatpak](https://docs.flatpak.org/en/latest/electron.html) guide. This repo **repacks your existing `linux-unpacked` Electron build** (self-contained binary). The Flathub-style alternative is **`org.electronjs.Electron2.BaseApp`** plus a **`zypak-wrapper`** launcher so Chromium is shared and sandbox integration matches upstream samples—that would be a larger manifest rewrite than the current “bundle what electron-builder produced” flow. + +The Flatpak wrapper runs Electron with **`--no-sandbox`** / **`--disable-setuid-sandbox`**: Chromium’s setuid `chrome-sandbox` cannot be root-owned inside a Flatpak, so process isolation for the UI relies on **Flatpak’s sandbox** instead (same pattern as many other Electron Flatpaks). **`--ozone-platform-hint=auto`** is passed so Wayland is used when the session supports it (still consider X11/Xwayland the most predictable default per the guide). + +#### Host tools when you install the Flatpak (Docker + FreeRDP) + +The setup wizard talks to the **host**: install and run these **outside** the Flatpak. The Flatpak does **not** ship the `docker` / `podman` CLIs; WinBoat runs them on the host with **`flatpak-spawn --host`** (see [`flatpak-host.ts`](src/renderer/lib/flatpak-host.ts)) while using the mounted **Docker/Podman sockets**. You still need **`--talk-name=org.freedesktop.Flatpak`** in the manifest (already set). + +| Check | Debian/Ubuntu-style hints | +|--------|---------------------------| +| **Docker Engine** | `sudo apt install docker.io` (or Docker’s official repo). | +| **Docker Compose v2** | `sudo apt install docker-compose-v2` (plugin: `docker compose version`). | +| **`docker` group** | `sudo usermod -aG docker "$USER"` then **log out and back in** (or reboot). | +| **Daemon running** | `sudo systemctl enable --now docker` then `docker ps`. | +| **FreeRDP 3.x** | Install **`xfreerdp3`** on the host, **or** `flatpak install --user flathub com.freerdp.FreeRDP` (WinBoat probes the host via `flatpak-spawn --host …`). Verify on the host: `xfreerdp3 --version` or `flatpak run com.freerdp.FreeRDP --command=xfreerdp -- --version`. | + +Use **Podman** instead of Docker if you prefer; the Flatpak exposes `xdg-run/podman` paths for the rootless socket. **Flathub:** Publishing on [Flathub](https://flathub.org/) is a separate submission ([author docs](https://docs.flathub.org/docs/for-app-authors/submission)). Reviewers treat apps that depend heavily on host services case-by-case; upstream maintenance expectation applies especially where emulation or host tooling is involved. The canonical manifest for packaging lives at [`flatpak/app.winboat.WinBoat.yml`](flatpak/app.winboat.WinBoat.yml). diff --git a/flatpak/app.winboat.WinBoat.yml b/flatpak/app.winboat.WinBoat.yml index 1f0434fc..8ae79d94 100755 --- a/flatpak/app.winboat.WinBoat.yml +++ b/flatpak/app.winboat.WinBoat.yml @@ -14,14 +14,19 @@ finish-args: - --socket=fallback-x11 - --socket=wayland - --socket=pulseaudio + - --socket=session-bus + - --socket=system-bus - --device=dri - --device=kvm - --filesystem=home - --filesystem=/run/docker.sock + - --filesystem=/var/run/docker.sock - --filesystem=xdg-run/docker - --filesystem=xdg-run/podman:create - --filesystem=xdg-run/podman - --talk-name=org.freedesktop.Flatpak + - --env=ELECTRON_TRASH=gio + - --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons modules: - name: winboat diff --git a/flatpak/winboat.sh b/flatpak/winboat.sh index 21e45c87..afcaa243 100755 --- a/flatpak/winboat.sh +++ b/flatpak/winboat.sh @@ -2,8 +2,11 @@ set -eu export TMPDIR="${XDG_RUNTIME_DIR:-/tmp}/app.${FLATPAK_ID:-app.winboat.WinBoat}" mkdir -p "${TMPDIR}" +# Trash integration (see https://docs.flatpak.org/en/latest/electron.html#sandbox-permissions) +export ELECTRON_TRASH="${ELECTRON_TRASH:-gio}" # Electron's setuid chrome-sandbox cannot be root-owned inside Flatpak; rely on Flatpak's sandbox instead. -SANDBOX_FLAGS="--no-sandbox --disable-setuid-sandbox" +# --ozone-platform-hint=auto: Wayland when available (experimental; see Flatpak Electron guide). +SANDBOX_FLAGS="--no-sandbox --disable-setuid-sandbox --ozone-platform-hint=auto" if [ -x /app/winboat/winboat ]; then exec /app/winboat/winboat ${SANDBOX_FLAGS} "$@"; fi if [ -x /app/winboat/WinBoat ]; then exec /app/winboat/WinBoat ${SANDBOX_FLAGS} "$@"; fi echo "WinBoat executable not found under /app/winboat" >&2 diff --git a/src/renderer/lib/containers/docker.ts b/src/renderer/lib/containers/docker.ts index 9f174af6..8e758036 100644 --- a/src/renderer/lib/containers/docker.ts +++ b/src/renderer/lib/containers/docker.ts @@ -13,10 +13,15 @@ import { } from "./container"; import YAML from "yaml"; import { execFileAsync, stringifyExecFile } from "../exec-helper"; +import { hostExec } from "../flatpak-host"; const path: typeof import("node:path") = require("node:path"); const fs: typeof import("node:fs") = require("node:fs"); +function dockerSpawn(args: string[]): { file: string; args: string[] } { + return hostExec("docker", args); +} + export type DockerSpecs = { dockerInstalled: boolean; dockerComposeInstalled: boolean; @@ -51,13 +56,14 @@ export class DockerContainer extends ContainerManager { args.push("-d"); } + const sp = dockerSpawn(args); try { - const { stderr } = await execFileAsync(this.executableAlias, args); + const { stderr } = await execFileAsync(sp.file, sp.args); if (stderr) { containerLogger.error(stderr); } } catch (e) { - containerLogger.error(`Failed to run compose command '${stringifyExecFile(this.executableAlias, args)}'`); + containerLogger.error(`Failed to run compose command '${stringifyExecFile(sp.file, sp.args)}'`); containerLogger.error(e); throw e; } @@ -65,11 +71,12 @@ export class DockerContainer extends ContainerManager { async container(action: ContainerAction): Promise { const args = ["container", action, this.containerName]; + const sp = dockerSpawn(args); try { - const { stdout } = await execFileAsync(this.executableAlias, args); + const { stdout } = await execFileAsync(sp.file, sp.args); containerLogger.info(`Container action '${action}' response: '${stdout}'`); } catch (e) { - containerLogger.error(`Failed to run container action '${stringifyExecFile(this.executableAlias, args)}'`); + containerLogger.error(`Failed to run container action '${stringifyExecFile(sp.file, sp.args)}'`); containerLogger.error(e); throw e; } @@ -78,9 +85,10 @@ export class DockerContainer extends ContainerManager { async port(): Promise { const args = ["port", this.containerName]; const ret = []; + const sp = dockerSpawn(args); try { - const { stdout } = await execFileAsync(this.executableAlias, args); + const { stdout } = await execFileAsync(sp.file, sp.args); for (const line of stdout.trim().split("\n")) { const parts = line.split("->").map(part => part.trim()); @@ -90,7 +98,7 @@ export class DockerContainer extends ContainerManager { ret.push(new ComposePortEntry(`${hostPart}:${containerPart}`)); } } catch (e) { - containerLogger.error(`Failed to run container action '${stringifyExecFile(this.executableAlias, args)}'`); + containerLogger.error(`Failed to run container action '${stringifyExecFile(sp.file, sp.args)}'`); containerLogger.error(e); throw e; } @@ -102,9 +110,10 @@ export class DockerContainer extends ContainerManager { async remove(): Promise { const args = ["rm", this.containerName]; + const sp = dockerSpawn(args); try { - await execFileAsync(this.executableAlias, args); + await execFileAsync(sp.file, sp.args); } catch (e) { containerLogger.error(`Failed to remove container '${this.containerName}'`); containerLogger.error(e); @@ -122,8 +131,9 @@ export class DockerContainer extends ContainerManager { dead: ContainerStatus.UNKNOWN, } as const; const args = ["inspect", "--format={{.State.Status}}", this.containerName]; + const sp = dockerSpawn(args); try { - const { stdout } = await execFileAsync(this.executableAlias, args); + const { stdout } = await execFileAsync(sp.file, sp.args); const status = stdout.trim() as keyof typeof statusMap; return statusMap[status]; } catch (e) { @@ -134,8 +144,9 @@ export class DockerContainer extends ContainerManager { async exists(): Promise { const args = ["ps", "-a", "--filter", `name=${this.containerName}`, "--format", "{{.Names}}"]; + const sp = dockerSpawn(args); try { - const { stdout: exists } = await execFileAsync(this.executableAlias, args); + const { stdout: exists } = await execFileAsync(sp.file, sp.args); return exists.includes("WinBoat"); } catch (e) { containerLogger.error( @@ -159,7 +170,8 @@ export class DockerContainer extends ContainerManager { }; try { - const { stdout: dockerOutput } = await execFileAsync("docker", ["--version"]); + const sp = dockerSpawn(["--version"]); + const { stdout: dockerOutput } = await execFileAsync(sp.file, sp.args); specs.dockerInstalled = !!dockerOutput; } catch (e) { console.error("Error checking for Docker installation:", e); @@ -167,7 +179,8 @@ export class DockerContainer extends ContainerManager { // Docker Compose plugin check with version validation try { - const { stdout: dockerComposeOutput } = await execFileAsync("docker", ["compose", "version"]); + const sp = dockerSpawn(["compose", "version"]); + const { stdout: dockerComposeOutput } = await execFileAsync(sp.file, sp.args); if (dockerComposeOutput) { // Example output: "Docker Compose version v2.35.1" // Example output 2: "Docker Compose version 2.36.2" @@ -187,15 +200,17 @@ export class DockerContainer extends ContainerManager { // Docker is running check try { - const { stdout: dockerOutput } = await execFileAsync("docker", ["ps"]); + const sp = dockerSpawn(["ps"]); + const { stdout: dockerOutput } = await execFileAsync(sp.file, sp.args); specs.dockerIsRunning = !!dockerOutput; } catch (e) { console.error("Error checking if Docker is running:", e); } - // Docker user group check + // Docker user group check (host groups when running as Flatpak) try { - const { stdout: userGroups } = await execFileAsync("id", ["-Gn"]); + const sp = hostExec("id", ["-Gn"]); + const { stdout: userGroups } = await execFileAsync(sp.file, sp.args); specs.dockerIsInUserGroups = userGroups.split(/\s+/).includes("docker"); } catch (e) { console.error("Error checking user groups for docker:", e); diff --git a/src/renderer/lib/containers/podman.ts b/src/renderer/lib/containers/podman.ts index 913da8c4..bb355daf 100644 --- a/src/renderer/lib/containers/podman.ts +++ b/src/renderer/lib/containers/podman.ts @@ -13,11 +13,16 @@ import YAML from "yaml"; import { capitalizeFirstLetter } from "../../utils/capitalize"; import { ComposePortEntry } from "../../utils/port"; import { concatEnv, execFileAsync, stringifyExecFile } from "../exec-helper"; +import { hostExec } from "../flatpak-host"; const path: typeof import("node:path") = require("node:path"); const fs: typeof import("node:fs") = require("node:fs"); const process: typeof import("process") = require("node:process"); +function podmanSpawn(args: string[]): { file: string; args: string[] } { + return hostExec("podman", args); +} + export type PodmanSpecs = { podmanInstalled: boolean; podmanComposeInstalled: boolean; @@ -77,15 +82,16 @@ export class PodmanContainer extends ContainerManager { args.push("-d"); } + const sp = podmanSpawn(args); try { - const { stderr } = await execFileAsync(this.executableAlias, args, { + const { stderr } = await execFileAsync(sp.file, sp.args, { env: concatEnv(process.env as { [key: string]: string }, COMPOSE_ENV_VARS), }); if (stderr) { containerLogger.error(stderr); } } catch (e) { - containerLogger.error(`Failed to run compose command '${stringifyExecFile(this.executableAlias, args)}'`); + containerLogger.error(`Failed to run compose command '${stringifyExecFile(sp.file, sp.args)}'`); containerLogger.error(e); throw e; } @@ -93,11 +99,12 @@ export class PodmanContainer extends ContainerManager { async container(action: ContainerAction): Promise { const args = ["container", action, this.containerName]; + const sp = podmanSpawn(args); try { - const { stdout } = await execFileAsync(this.executableAlias, args); + const { stdout } = await execFileAsync(sp.file, sp.args); containerLogger.info(`Container action '${action}' response: '${stdout}'`); } catch (e) { - containerLogger.error(`Failed to run container action '${stringifyExecFile(this.executableAlias, args)}'`); + containerLogger.error(`Failed to run container action '${stringifyExecFile(sp.file, sp.args)}'`); containerLogger.error(e); throw e; } @@ -106,9 +113,10 @@ export class PodmanContainer extends ContainerManager { async port(): Promise { const args = ["port", this.containerName]; const ret = []; + const sp = podmanSpawn(args); try { - const { stdout } = await execFileAsync(this.executableAlias, args); + const { stdout } = await execFileAsync(sp.file, sp.args); for (const line of stdout.trim().split("\n")) { const parts = line.split("->").map(part => part.trim()); @@ -118,7 +126,7 @@ export class PodmanContainer extends ContainerManager { ret.push(new ComposePortEntry(`${hostPart}:${containerPart}`)); } } catch (e) { - containerLogger.error(`Failed to run container action '${stringifyExecFile(this.executableAlias, args)}'`); + containerLogger.error(`Failed to run container action '${stringifyExecFile(sp.file, sp.args)}'`); containerLogger.error(e); throw e; } @@ -130,9 +138,10 @@ export class PodmanContainer extends ContainerManager { async remove(): Promise { const args = ["rm", this.containerName]; + const sp = podmanSpawn(args); try { - await execFileAsync(this.executableAlias, args); + await execFileAsync(sp.file, sp.args); } catch (e) { containerLogger.error(`Failed to remove container '${this.containerName}'`); containerLogger.error(e); @@ -153,9 +162,10 @@ export class PodmanContainer extends ContainerManager { dead: ContainerStatus.UNKNOWN, } as const; const args = ["inspect", "--format={{.State.Status}}", this.containerName]; + const sp = podmanSpawn(args); try { - const { stdout } = await execFileAsync(this.executableAlias, args); + const { stdout } = await execFileAsync(sp.file, sp.args); const status = stdout.trim() as keyof typeof statusMap; return statusMap[status]; } catch (e) { @@ -166,8 +176,9 @@ export class PodmanContainer extends ContainerManager { async exists(): Promise { const args = ["ps", "-a", "--filter", `name=${this.containerName}`, "--format", "{{.Names}}"]; + const sp = podmanSpawn(args); try { - const { stdout: exists } = await execFileAsync(this.executableAlias, args); + const { stdout: exists } = await execFileAsync(sp.file, sp.args); return exists.includes(this.containerName); } catch (e) { containerLogger.error( @@ -189,7 +200,8 @@ export class PodmanContainer extends ContainerManager { }; try { - const { stdout: podmanOutput } = await execFileAsync("podman", ["--version"]); + const sp = podmanSpawn(["--version"]); + const { stdout: podmanOutput } = await execFileAsync(sp.file, sp.args); specs.podmanInstalled = !!podmanOutput; } catch (e) { containerLogger.error("Error checking podman version"); @@ -197,7 +209,8 @@ export class PodmanContainer extends ContainerManager { } try { - const { stdout: podmanComposeOutput } = await execFileAsync("podman", ["compose", "--version"], { + const sp = podmanSpawn(["compose", "--version"]); + const { stdout: podmanComposeOutput } = await execFileAsync(sp.file, sp.args, { env: concatEnv(process.env as { [key: string]: string }, COMPOSE_ENV_VARS), }); specs.podmanComposeInstalled = !!podmanComposeOutput; diff --git a/src/renderer/lib/flatpak-host.ts b/src/renderer/lib/flatpak-host.ts new file mode 100644 index 00000000..5b07a8bc --- /dev/null +++ b/src/renderer/lib/flatpak-host.ts @@ -0,0 +1,17 @@ +const process: typeof import("process") = require("node:process"); + +/** True when WinBoat runs as a Flatpak (e.g. app.winboat.WinBoat). */ +export function runningInsideFlatpak(): boolean { + return Boolean(process.env.FLATPAK_ID); +} + +/** + * Run a host binary from inside Flatpak. The Docker/Podman CLIs and host `id` are not in the sandbox PATH; + * the engine is reached via mounted sockets, so commands must execute on the host. + */ +export function hostExec(cmd: string, args: readonly string[]): { file: string; args: string[] } { + if (runningInsideFlatpak()) { + return { file: "flatpak-spawn", args: ["--host", cmd, ...args] }; + } + return { file: cmd, args: [...args] }; +} diff --git a/src/renderer/lib/winboat.ts b/src/renderer/lib/winboat.ts index d6d52f19..9b07cc88 100644 --- a/src/renderer/lib/winboat.ts +++ b/src/renderer/lib/winboat.ts @@ -18,7 +18,8 @@ import { MultiMonitorMode, WinboatConfig } from "./config"; import { QMPManager } from "./qmp"; import { assert } from "@vueuse/core"; import { setIntervalImmediately } from "../utils/interval"; -import { ExecFileAsyncError } from "./exec-helper"; +import { ExecFileAsyncError, execFileAsync } from "./exec-helper"; +import { hostExec } from "./flatpak-host"; import { ContainerManager, ContainerStatus } from "./containers/container"; import { CommonPorts, ContainerRuntimes, createContainer, getActiveHostPort } from "./containers/common"; @@ -598,7 +599,10 @@ export class Winboat { logger.error("Volume not supported on podman runtime"); } // In this case we have a volume (legacy) - await execAsync("docker volume rm winboat_data"); + { + const sp = hostExec("docker", ["volume", "rm", "winboat_data"]); + await execFileAsync(sp.file, sp.args); + } console.info("Removed volume"); } else { const storageFolder = storage?.split(":").at(0) ?? null; diff --git a/src/renderer/utils/getFreeRDP.ts b/src/renderer/utils/getFreeRDP.ts index 2930bd6b..8ee12af0 100644 --- a/src/renderer/utils/getFreeRDP.ts +++ b/src/renderer/utils/getFreeRDP.ts @@ -1,4 +1,5 @@ import { execFileAsync, stringifyExecFile } from "../lib/exec-helper"; +import { runningInsideFlatpak } from "../lib/flatpak-host"; export class FreeRDPInstallation { file: string; @@ -21,6 +22,13 @@ export class FreeRDPInstallation { } } +/** Host FreeRDP when WinBoat itself runs as a Flatpak (no host binaries in PATH). */ +const freeRDPInstallationsFlatpak = [ + new FreeRDPInstallation("flatpak-spawn", ["--host", "xfreerdp3"]), + new FreeRDPInstallation("flatpak-spawn", ["--host", "xfreerdp"]), + new FreeRDPInstallation("flatpak-spawn", ["--host", "flatpak", "run", "--command=xfreerdp", "com.freerdp.FreeRDP"]), +]; + const freeRDPInstallations = [ new FreeRDPInstallation("xfreerdp3"), new FreeRDPInstallation("xfreerdp"), @@ -32,7 +40,10 @@ const freeRDPInstallations = [ */ export async function getFreeRDP() { const VERSION_3_STRING = "version 3."; - for (let installation of freeRDPInstallations) { + const candidates = runningInsideFlatpak() + ? [...freeRDPInstallationsFlatpak, ...freeRDPInstallations] + : freeRDPInstallations; + for (let installation of candidates) { try { const shellOutput = await installation.exec(["--version"]); if (shellOutput.stdout.includes(VERSION_3_STRING)) {