diff --git a/README.md b/README.md index 346491e4..f45fb1d8 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,9 @@ You can download the latest Linux builds under the [Releases](https://github.com - **.rpm:** The intended format for Fedora based distributions ## Known Issues About Container Runtimes -- Podman is **unsupported** for now - Docker Desktop is **unsupported** for now - Distros that emulate Docker through a Podman socket are **unsupported** -- Any rootless containerization solution is currently **unsupported** +- Rootless podman cannot do USB passthrough. ## Building WinBoat - For building you need to have NodeJS and Go installed on your system diff --git a/src/renderer/lib/config.ts b/src/renderer/lib/config.ts index d8339a64..840f45dd 100644 --- a/src/renderer/lib/config.ts +++ b/src/renderer/lib/config.ts @@ -12,6 +12,7 @@ export type WinboatConfigObj = { customApps: WinApp[] experimentalFeatures: boolean multiMonitor: number + containerRuntime: string }; const defaultConfig: WinboatConfigObj = { @@ -22,6 +23,7 @@ const defaultConfig: WinboatConfigObj = { customApps: [], experimentalFeatures: false, multiMonitor: 0, + containerRuntime: "docker", }; export class WinboatConfig { @@ -101,4 +103,4 @@ export class WinboatConfig { return { ...defaultConfig }; } } -} \ No newline at end of file +} diff --git a/src/renderer/lib/install.ts b/src/renderer/lib/install.ts index 88ae6dea..7b2b582b 100644 --- a/src/renderer/lib/install.ts +++ b/src/renderer/lib/install.ts @@ -9,6 +9,7 @@ const path: typeof import('path') = require('path'); const { promisify }: typeof import('util') = require('util'); const nodeFetch: typeof import('node-fetch').default = require('node-fetch'); const remote: typeof import('@electron/remote') = require('@electron/remote'); +import { WinboatConfig } from './config'; const execAsync = promisify(exec); const logger = createLogger(path.join(WINBOAT_DIR, 'install.log')); @@ -83,12 +84,14 @@ export class InstallManager { emitter: Emitter; state: InstallState; preinstallMsg: string; + wbConfig: WinboatConfig | null constructor(conf: InstallConfiguration) { this.conf = conf; this.state = InstallStates.IDLE; this.preinstallMsg = "" this.emitter = createNanoEvents(); + this.wbConfig = new WinboatConfig(); } changeState(newState: InstallState) { @@ -240,7 +243,7 @@ export class InstallManager { // Start the container try { // execSync(`docker compose -f ${composeFilePath} up -d`, { stdio: 'inherit' }); - const { stdout, stderr } = await execAsync(`docker compose -f ${composeFilePath} up -d`); + const { stdout, stderr } = await execAsync(`${this.wbConfig!.config.containerRuntime} compose -f ${composeFilePath} up -d`); if (stderr) { logger.error(stderr); } @@ -331,8 +334,9 @@ export class InstallManager { export async function isInstalled() { // Check if a docker container named WinBoat exists + const wbConfig: WinboatConfig | null = new WinboatConfig(); try { - const { stdout: res } = await execAsync('docker ps -a --filter "name=WinBoat" --format "{{.Names}}"'); + const { stdout: res } = await execAsync(`${wbConfig!.config.containerRuntime} ps -a --filter "name=WinBoat" --format "{{.Names}}"`); return res.includes('WinBoat'); } catch(e) { logger.error("Failed to get WinBoat status, is Docker installed?"); diff --git a/src/renderer/lib/specs.ts b/src/renderer/lib/specs.ts index 953226ab..a37f14e2 100644 --- a/src/renderer/lib/specs.ts +++ b/src/renderer/lib/specs.ts @@ -1,6 +1,7 @@ import { getFreeRDP } from '../utils/getFreeRDP'; const fs: typeof import('fs') = require('fs'); const os: typeof import('os') = require('os'); +import { WinboatConfig } from './config'; const { exec }: typeof import('child_process') = require('child_process'); const { promisify }: typeof import('util') = require('util'); const execAsync = promisify(exec); @@ -20,6 +21,7 @@ export const defaultSpecs: Specs = { cpuCores: 0, ramGB: 0, kvmEnabled: false, + usingDocker: true, dockerInstalled: false, dockerComposeInstalled: false, dockerIsRunning: false, @@ -30,6 +32,9 @@ export const defaultSpecs: Specs = { export async function getSpecs() { const specs: Specs = { ...defaultSpecs }; + let wbConfig: WinboatConfig | null = new WinboatConfig(); // Instantiate singleton class + specs.usingDocker = (wbConfig!.config.containerRuntime == "docker") + // Physical CPU cores check try { const res = (await execAsync('lscpu -p | egrep -v "^#" | sort -u -t, -k 2,4 | wc -l')).stdout; @@ -59,7 +64,7 @@ export async function getSpecs() { // Docker check try { - const { stdout: dockerOutput } = await execAsync('docker --version'); + let { stdout: dockerOutput } = await execAsync(`${wbConfig!.config.containerRuntime} --version`); specs.dockerInstalled = !!dockerOutput; } catch (e) { console.error('Error checking for Docker installation:', e); @@ -67,7 +72,7 @@ export async function getSpecs() { // Docker Compose plugin check with version validation try { - const { stdout: dockerComposeOutput } = await execAsync('docker compose version'); + const { stdout: dockerComposeOutput } = await execAsync(`${wbConfig!.config.containerRuntime} compose version`); if (dockerComposeOutput) { // Example output: "Docker Compose version v2.35.1" // Example output 2: "Docker Compose version 2.36.2" @@ -87,16 +92,16 @@ export async function getSpecs() { // Docker is running check try { - const { stdout: dockerOutput } = await execAsync('docker ps'); + const { stdout: dockerOutput } = await execAsync(`${wbConfig!.config.containerRuntime} ps`); specs.dockerIsRunning = !!dockerOutput; } catch (e) { - console.error('Error checking if Docker is running:', e); + console.error('Error checking if Container Manager is running:', e); } // Docker user group check try { const userGroups = (await execAsync('id -Gn')).stdout; - specs.dockerIsInUserGroups = userGroups.split(/\s+/).includes('docker'); + specs.dockerIsInUserGroups = (wbConfig!.config.containerRuntime != "docker" || userGroups.split(/\s+/).includes('docker')); } catch (e) { console.error('Error checking user groups for docker:', e); } diff --git a/src/renderer/lib/winboat.ts b/src/renderer/lib/winboat.ts index ed08a122..4483d9c4 100644 --- a/src/renderer/lib/winboat.ts +++ b/src/renderer/lib/winboat.ts @@ -374,10 +374,10 @@ export class Winboat { async getContainerStatus() { try { - const { stdout: _containerStatus } = await execAsync(`docker inspect --format="{{.State.Status}}" WinBoat`); + const { stdout: _containerStatus } = await execAsync(`${this.#wbConfig!.config.containerRuntime} inspect --format="{{.State.Status}}" WinBoat`); return _containerStatus.trim() as ContainerStatusValue; } catch(e) { - console.error("Failed to get container status, most likely we are in the process of resetting"); + console.error(`Failed to get container status, most likely we are in the process of resetting ${this.#wbConfig!.config.containerRuntime}`); return ContainerStatus.Dead; } } @@ -448,7 +448,7 @@ export class Winboat { logger.info("Starting WinBoat container..."); this.containerActionLoading.value = true; try { - const { stdout } = await execAsync("docker container start WinBoat"); + const { stdout } = await execAsync(`${this.#wbConfig!.config.containerRuntime} container start WinBoat`); logger.info(`Container response: ${stdout}`); } catch(e) { logger.error("There was an error performing the container action."); @@ -463,7 +463,7 @@ export class Winboat { logger.info("Stopping WinBoat container..."); this.containerActionLoading.value = true; try { - const { stdout } = await execAsync("docker container stop WinBoat"); + const { stdout } = await execAsync(`${this.#wbConfig!.config.containerRuntime} container stop WinBoat`); logger.info(`Container response: ${stdout}`); } catch(e) { logger.error("There was an error performing the container action."); @@ -478,7 +478,7 @@ export class Winboat { logger.info("Pausing WinBoat container..."); this.containerActionLoading.value = true; try { - const { stdout } = await execAsync("docker container pause WinBoat"); + const { stdout } = await execAsync(`${this.#wbConfig!.config.containerRuntime} container pause WinBoat`); logger.info(`Container response: ${stdout}`); // TODO: The heartbeat check should set this, but it doesn't because normal fetch timeout doesn't exist // Fix it once you change fetch to something else @@ -496,7 +496,7 @@ export class Winboat { logger.info("Unpausing WinBoat container..."); this.containerActionLoading.value = true; try { - const { stdout } = await execAsync("docker container unpause WinBoat"); + const { stdout } = await execAsync(`${this.#wbConfig!.config.containerRuntime} container unpause WinBoat`); logger.info(`Container response: ${stdout}`); } catch(e) { logger.error("There was an error performing the container action."); @@ -519,7 +519,7 @@ export class Winboat { } // 1. Compose down the current container - await execAsync(`docker compose -f ${composeFilePath} down`); + await execAsync(`${this.#wbConfig!.config.containerRuntime} compose -f ${composeFilePath} down`); // 2. Create a backup directory if it doesn't exist const backupDir = path.join(WINBOAT_DIR, 'backup'); @@ -539,7 +539,7 @@ export class Winboat { logger.info(`Wrote new compose file to: ${composeFilePath}`); // 5. Deploy the container with the new compose file - await execAsync(`docker compose -f ${composeFilePath} up -d`); + await execAsync(`${this.#wbConfig!.config.containerRuntime} compose -f ${composeFilePath} up -d`); logger.info("Replace compose config completed, successfully deployed new container"); @@ -554,7 +554,7 @@ export class Winboat { console.info("Stopped container"); // 2. Remove the container - await execAsync("docker rm WinBoat") + await execAsync(`${this.#wbConfig!.config.containerRuntime} rm WinBoat`) console.info("Removed container") // 3. Remove the container volume or folder @@ -562,7 +562,7 @@ export class Winboat { const storage = compose.services.windows.volumes.find(vol => vol.includes('/storage')); if (storage?.startsWith("data:")) { // In this case we have a volume (legacy) - await execAsync("docker volume rm winboat_data"); + await execAsync(`${this.#wbConfig!.config.containerRuntime} volume rm winboat_data`); console.info("Removed volume"); } else { const storageFolder = storage?.split(":").at(0) ?? null; @@ -716,4 +716,4 @@ export class Winboat { get hasQMPInterval() { return this.#qmpInterval !== null; } -} \ No newline at end of file +} diff --git a/src/renderer/utils/getFreeRDP.ts b/src/renderer/utils/getFreeRDP.ts index 4c6025bc..26c64f3d 100644 --- a/src/renderer/utils/getFreeRDP.ts +++ b/src/renderer/utils/getFreeRDP.ts @@ -19,4 +19,4 @@ export async function getFreeRDP() { } catch {} } return null; -} \ No newline at end of file +} diff --git a/src/renderer/views/SetupUI.vue b/src/renderer/views/SetupUI.vue index 7a6d1ac4..c53823c0 100644 --- a/src/renderer/views/SetupUI.vue +++ b/src/renderer/views/SetupUI.vue @@ -79,7 +79,7 @@
  • - Docker installed + Container manager installed How?
  • @@ -88,7 +88,7 @@ Docker Compose v2 installed How?
  • -
  • +
  • User added to the docker group @@ -97,7 +97,7 @@ How?
  • -
  • +
  • Docker daemon is running