Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.git
.idea
.vscode
build
dist
guest_server/winboat_guest_server.exe
guest_server/winboat_guest_server.zip
node_modules
result
temp
32 changes: 31 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,21 @@ on:
- all
- linux-only
- guest-server-only
with_flatpak:
description: "With flatpak"
required: true
type: boolean
default: false
jobs:
build:
runs-on: ubuntu-22.04
if: >-
(github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.build_type != 'guest-server-only')
(github.event_name == 'workflow_dispatch' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux-only'))
outputs:
name: ${{ steps.meta.outputs.NAME }}
version: ${{ steps.meta.outputs.VERSION }}
arch: ${{ steps.meta.outputs.ARCH }}
steps:
- name: Checkout code
uses: actions/checkout@v5
Expand Down Expand Up @@ -123,6 +132,27 @@ jobs:
# dist/latest-linux.yml
# dist/linux-unpacked/resources/guest_server/winboat_guest_server.zip
# if-no-files-found: ignore
build-flatpak:
runs-on: ubuntu-22.04
if: github.event_name == 'workflow_dispatch' && (github.event.inputs.with_flatpak == 'true')
env:
DOCKER_BUILD_SUMMARY: false
needs: build
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
buildkitd-flags: "--allow-insecure-entitlement=security.insecure"
- name: Build flatpak
uses: docker/bake-action@v6
with:
targets: flatpak
allow: security.insecure
- name: Upload flatpak bundle
uses: actions/upload-artifact@v4
with:
name: ${{needs.build.outputs.name}}-${{needs.build.outputs.version}}-${{needs.build.outputs.arch}}.flatpak
path: build/winboat.flatpak
guest-server-only:
runs-on: ubuntu-22.04
if: github.event_name == 'workflow_dispatch' && github.event.inputs.build_type == 'guest-server-only'
Expand Down
32 changes: 32 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
ARG GO_IMAGE=golang:1.25-alpine
ARG FLATPAK_IMAGE=ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08

FROM ${GO_IMAGE} AS go-sources
WORKDIR /workdir
COPY guest_server .
RUN go run github.com/dennwc/flatpak-go-mod@v0.1.0 -dest-pref main/guest_server/ -json .

FROM ${FLATPAK_IMAGE} AS js-sources
WORKDIR /workdir
COPY package-lock.json .
RUN flatpak-node-generator npm package-lock.json -o output.json --electron-node-headers

FROM scratch AS dependency-manifests
COPY --from=go-sources /workdir/modules.txt modules.txt
COPY --from=go-sources /workdir/go.mod.json go-sources.json
COPY --from=js-sources /workdir/output.json js-sources.json

FROM ${FLATPAK_IMAGE} AS flatpak
ARG FLATPAK_REPO=https://flathub.org/repo/flathub.flatpakrepo
WORKDIR /workdir
COPY . .
COPY --from=dependency-manifests /* flatpak/
RUN flatpak remote-add --user flathub ${FLATPAK_REPO}
RUN --security=insecure flatpak-builder \
--user --disable-rofiles-fuse --install-deps-from=flathub \
--repo=/tmp/repo /tmp/builddir flatpak/app.winboat.Winboat.yml
RUN flatpak build-bundle /tmp/repo winboat.flatpak app.winboat.Winboat \
--runtime-repo=${FLATPAK_REPO}

FROM scratch AS flatpak-bundle
COPY --from=flatpak /workdir/winboat.flatpak /
13 changes: 13 additions & 0 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
group "default" {
targets = ["flatpak"]
}

target "flatpak" {
description = "Build a flatpak bundle"
output = [{
type = "local"
dest = "build"
}]
target = "flatpak-bundle"
entitlements = ["security.insecure"] // req. by flatpak-builder
}
103 changes: 103 additions & 0 deletions flatpak/app.winboat.Winboat.metainfo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>app.winboat.Winboat</id>

<metadata_license>MIT</metadata_license>
<project_license>MIT</project_license>
<content_rating type="oars-1.1" />
<developer_name>WinBoat developers</developer_name>

<categories>
<category>Emulator</category>
<category>Utility</category>
</categories>

<name>WinBoat</name>
<summary>Run Windows apps on Linux with seamless integration</summary>
<description>
<p>Features</p>
<ul>
<li>
Elegant Interface: Sleek and intuitive interface that
seamlessly integrates Windows into your Linux desktop
environment, making it feel like a native experience
</li>
<li>
Automated Installs: Simple installation process through our
interface - pick your preferences and specs and let us
handle the rest
</li>
<li>
Run Any App: If it runs on Windows, it can run on
WinBoat. Enjoy the full range of Windows applications as
native OS-level windows in your Linux environment
</li>
<li>
Full Windows Desktop: Access the complete Windows desktop
experience when you need it, or run individual apps
seamlessly integrated into your Linux workflow
</li>
<li>
Filesystem Integration: Your home directory is mounted in
Windows, allowing easy file sharing between the two systems
without any hassle
</li>
<li>
And many more: Smartcard passthrough, resource monitoring,
and more features being added regularly
</li>
</ul>
</description>

<url type="homepage">https://github.com/TibixDev/winboat</url>
<url type="bugtracker">https://github.com/TibixDev/winboat/issues</url>

<releases>
<release version="0.9.0" date="2025-11-24">
<url type="details">https://github.com/TibixDev/winboat/releases/tag/v0.9.0</url>
<description>
<p>Features:</p>
<ul>
<li>Podman is now supported, you can pick it during installation, however USB passthrough on Podman is not supported yet</li>
<li>UWP app support has been added, now you can see and start all your UWP apps</li>
<li>A Custom App can now be created from an existing one if you'd like to modify it</li>
<li>Apps can now be filtered</li>
<li>Apps can now be launched via the context menu</li>
<li>You can now adjust the Application Scaling in Configuration</li>
<li>New WinBoat installations will now reserve the port range 47270 - 47370 on Docker for all used services, this is to avoid common ports and port conflicts in the future. In the meantime Podman does not support port ranges in compose files, so we're allocating random ports</li>
<li>You can now add new or replace any existing FreeRDP arguments in Configuration</li>
<li>A wm-class prefix has been added for folks who wanna style WinBoat FreeRDP windows in their WM</li>
<li>Windows will now auto-synchronize the clock on new installations</li>
<li>The default install path has been set to ~/winboat, of course you can still change it to whatever you prefer</li>
<li>You can now disable animations within WinBoat if you're sensitive to motion or your compositor doesn't play nicely with Electron</li>
</ul>
<p>Fixes:</p>
<ul>
<li>If you're using a Custom ISO for installation, that ISO will get auto-unmounted afterwards so the next time you start WinBoat with that ISO deleted, it will work properly</li>
<li>Fixed the CPU usage spiking when RDP monitoring is enabled</li>
<li>Corrected a few typos</li>
</ul>
<p>Misc:</p>
<ul>
<li>dockur/windows version upgraded to 5.14</li>
<li>All WinBoat ports are now bound by default to 127.0.0.1</li>
<li>Improved the security of the Guest Server (thanks @mrsheepsheep and @matt0x00)</li>
</ul>
</description>
</release>
</releases>

<screenshots>
<screenshot type="default">
<image>https://github.com/TibixDev/winboat/blob/v0.9.0/gh-assets/features/feat_dash.png</image>
</screenshot>
<screenshot>
<image>https://github.com/TibixDev/winboat/blob/v0.9.0/gh-assets/features/feat_apps.png</image>
</screenshot>
<screenshot>
<image>https://github.com/TibixDev/winboat/blob/v0.9.0/gh-assets/features/feat_native.png</image>
</screenshot>
</screenshots>

<launchable type="desktop-id">app.winboat.Winboat.desktop</launchable>
</component>
65 changes: 65 additions & 0 deletions flatpak/app.winboat.Winboat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
app-id: app.winboat.Winboat
runtime: org.freedesktop.Platform
runtime-version: '25.08'
sdk: org.freedesktop.Sdk
base: org.electronjs.Electron2.BaseApp
base-version: '25.08'
sdk-extensions:
- org.freedesktop.Sdk.Extension.node24
- org.freedesktop.Sdk.Extension.golang
command: winboat
separate-locales: false
finish-args:
- --share=ipc
- --device=dri
- --device=kvm
- --socket=x11 # (not fallback-x11 because of xfreerdp)
- --socket=wayland
- --socket=pulseaudio
- --share=network
- --talk-name=org.freedesktop.Flatpak
- --env=ELECTRON_OZONE_PLATFORM_HINT=auto
build-options:
append-path: /usr/lib/sdk/node24/bin
env:
NPM_CONFIG_LOGLEVEL: info
modules:
- name: winboat
buildsystem: simple
build-options:
append-path: /usr/lib/sdk/node24/bin
env:
XDG_CACHE_HOME: /run/build/winboat/flatpak-node/cache
npm_config_cache: /run/build/winboat/flatpak-node/npm-cache
build-commands:
- |
. /usr/lib/sdk/golang/enable.sh
. ../flatpak-node/electron-builder-arch-args.sh
for f in ../flatpak-node/cache/node-gyp/*/installVersion; do echo 11 >"$f"; done
ln -s $XDG_CACHE_HOME/node-gyp $HOME/.electron-gyp
npm install --offline
npm run --offline build:linux-gs -- $ELECTRON_BUILDER_ARCH_ARGS --dir
- cp -a dist/linux*unpacked /app/main
- install -Dm755 ../run.sh /app/bin/winboat
- install -Dm644 icons/winboat_logo.svg /app/share/icons/hicolor/scalable/apps/${FLATPAK_ID}.svg
- install -m644 -Dt /app/share/applications/ ../${FLATPAK_ID}.desktop
subdir: main
sources:
- type: dir
path: ..
dest: main
- js-sources.json
- go-sources.json
- type: script
dest-filename: run.sh
commands:
- zypak-wrapper.sh /app/main/winboat "$@"
- type: inline
contents: |
[Desktop Entry]
Name=WinBoat
Type=Application
Exec=winboat
Terminal=false
Icon=app.winboat.Winboat
dest-filename: app.winboat.Winboat.desktop
10 changes: 8 additions & 2 deletions src/renderer/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
const os: typeof import("os") = require("node:os");
const path: typeof import("path") = require("node:path");
const process: typeof import("process") = require("node:process");

// Should be {home}/.winboat
export const WINBOAT_DIR = path.join(os.homedir(), ".winboat");
// Should be $HOME/.winboat (or $XDG_DATA_HOME/winboat in flatpak)
export const WINBOAT_DIR = process.env.FLATPAK_ID
? path.join(process.env.XDG_STATE_HOME, "winboat")
: path.join(os.homedir(), ".winboat");
export const DEFAULT_VM_DATA_DIR = process.env.FLATPAK_ID
? path.join(process.env.XDG_DATA_HOME, "winboat")
: path.join(os.homedir(), "winboat");
export const DEFAULT_HOMEBREW_DIR = path.join(os.homedir(), "../linuxbrew/.linuxbrew/bin");

export const WINDOWS_VERSIONS = {
Expand Down
29 changes: 27 additions & 2 deletions src/renderer/lib/exec-helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { execFile }: typeof import("child_process") = require("node:child_process");
const process: typeof import("process") = require("node:process");
const child_process: typeof import("child_process") = require("node:child_process");
const { promisify }: typeof import("util") = require("node:util");

export type ExecFileAsyncError = {
Expand All @@ -12,7 +13,31 @@ export type ExecFileAsyncError = {
stack: string;
};

export const execFileAsync = promisify(execFile);
const doExecFile = promisify(child_process.execFile);

function keepEnv(varName) {
return `--env=${varName}=${process.env[varName]}`;
}

export function execFileAsync(file, args, options) {
if (process.env.FLATPAK_ID) {
return doExecFile("flatpak-spawn", [
keepEnv("DISPLAY"),
keepEnv("WAYLAND_DISPLAY"),
"--host", file
].concat(args || []), options);
} else {
return doExecFile(file, args, options);
}
}

export function execFileSync(file, args, options) {
if (process.env.FLATPAK_ID) {
return child_process.execFileSync("flatpak-spawn", ["--host", file].concat(args || []), options);
} else {
return child_process.execFileSync(file, args, options);
}
}

export function stringifyExecFile(file: string, args: string[]): string {
let result = `${file}`;
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/lib/usbmanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { type Ref, ref, watch } from "vue";
import { logger, Winboat } from "./winboat";
import { WinboatConfig } from "./config";
import { assert } from "@vueuse/core";
import { execFileSync } from "./exec-helper";

const { usb, getDeviceList }: typeof import("usb") = require("usb");
const fs: typeof import("node:fs") = require("node:fs");
const { execFileSync }: typeof import("node:child_process") = require("node:child_process");
const remote: typeof import("@electron/remote") = require("@electron/remote");
const path: typeof import("node:path") = require("node:path");

Expand Down
5 changes: 2 additions & 3 deletions src/renderer/lib/winboat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { WinboatConfig } from "./config";
import { QMPManager } from "./qmp";
import { assert } from "@vueuse/core";
import { setIntervalImmediately } from "../utils/interval";
import { ExecFileAsyncError } from "./exec-helper";
import { execFileAsync, ExecFileAsyncError } from "./exec-helper";
import { ContainerManager, ContainerStatus } from "./containers/container";
import { CommonPorts, ContainerRuntimes, createContainer, getActiveHostPort } from "./containers/common";

Expand All @@ -32,7 +32,6 @@ const remote: typeof import("@electron/remote") = require("@electron/remote");
const FormData: typeof import("form-data") = require("form-data");
const argon2: typeof import("argon2") = require("argon2");

const execAsync = promisify(exec);
const USAGE_PATH = path.join(WINBOAT_DIR, "appUsage.json");
export const logger = createLogger(path.join(WINBOAT_DIR, "winboat.log"));

Expand Down Expand Up @@ -584,7 +583,7 @@ 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");
await execFileAsync("docker", ["volume", "rm", "winboat_data"]);
console.info("Removed volume");
} else {
const storageFolder = storage?.split(":").at(0) ?? null;
Expand Down
Loading