Merge pull request #681 from silentrald/bugfix/nixos

[bugfix] fixed issues with NixOS
This commit is contained in:
MathieuG-P
2024-12-08 14:29:54 +01:00
committed by GitHub
8 changed files with 92 additions and 45 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ const config = {
// Read/write home directory access
"--filesystem=~/BSManager:create", // Default BSManager installation folder
"--filesystem=~/.steam/steam/steamapps:ro", // for the libraryfolders.vdf
"--filesystem=~/.steam/steam/steamapps/common:ro", // Steam game folder
"--filesystem=~/.steam/steam/steamapps/common:create", // Steam game folder
"--filesystem=~/.steam/steam/steamapps/common/Beat Saber:create", // For installing mods/maps to original Beat Saber version
// Allow communication with network
"--share=network",
+8 -2
View File
@@ -196,8 +196,14 @@ ifDescribe(IS_LINUX)("Test os.helpers isProcessRunning", () => {
const running = await isProcessRunning(`bs-manager-${crypto.randomUUID()}`);
expect(running).toBe(false);
// No errors received
expect(logSpy).toHaveBeenCalledTimes(0);
// Throws because grep couldn't find any process with that name
expect(logSpy).toHaveBeenCalledTimes(1);
});
it("Empty process name", async () => {
const running = await isProcessRunning("");
expect(running).toBe(false);
expect(logSpy).toHaveBeenCalledTimes(0);
})
});
+22 -7
View File
@@ -3,9 +3,6 @@ import log from "electron-log";
import psList from "ps-list";
import { IS_FLATPAK } from "main/constants";
// There are 2 erroneous lines ps | grep which is both the ps and grep calls themselves
const MIN_PROCESS_COUNT_LINUX = 2;
type LinuxOptions = {
// Add the prefix to the command
// eg. command - "./Beat Saber.exe" --no-yeet, prefix - "path/to/proton" run
@@ -101,14 +98,24 @@ export function bsmExec(command: string, options?: BsmExecOptions): Promise<{
});
}
// Transform command from "steam" to "[s]team"
// NOTE: Can add an option to isProcessRunning/getProcessId to ignore this transformation
// in the future if needed
const transformProcessNameForPS = (name: string) => `[${name.at(0)}]${name.substring(1)}`;
async function isProcessRunningLinux(name: string): Promise<boolean> {
if (!name) {
return false;
}
try {
const { stdout: count } = await bsmExec(`ps awwxo args | grep -c "${name}"`, {
const processName = transformProcessNameForPS(name);
const { stdout: count } = await bsmExec(`ps awwxo args | grep -c "${processName}"`, {
log: true,
flatpak: { host: IS_FLATPAK },
});
return +count.trim() > MIN_PROCESS_COUNT_LINUX;
return +count.trim() > 0;
} catch(error) {
log.error(error);
return false;
@@ -143,14 +150,22 @@ async function isProcessRunningWindows(name: string): Promise<boolean> {
}
async function getProcessIdLinux(name: string): Promise<number | null> {
if (!name) {
return null;
}
try {
const { stdout } = await bsmExec(`ps awwxo pid,args | grep "${name}"`, {
const processName = transformProcessNameForPS(name);
const { stdout } = await bsmExec(`ps awwxo pid,args | grep "${processName}"`, {
log: true,
flatpak: { host: IS_FLATPAK },
});
if (!stdout) {
return null;
}
const line = stdout.split("\n")
.slice(0, -MIN_PROCESS_COUNT_LINUX)
.map(line => line.trimStart())
.find(line => line.includes(name) && !line.includes("grep"));
return line ? +line.split(" ").at(0) : null;
@@ -52,7 +52,7 @@ export abstract class AbstractLauncherService {
spawnOptions.shell = true; // For windows to spawn properly
return bsmSpawn(`"${bsExePath}"`, {
args, options: spawnOptions, log: true,
linux: { prefix: this.linux.getProtonCommand() },
linux: { prefix: this.linux.getProtonPrefix() },
flatpak: {
host: IS_FLATPAK,
env: [
@@ -106,7 +106,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
// Linux setup
if (process.platform === "linux") {
this.linux.setupLaunch(launchOptions, steamPath, bsFolderPath, env);
await this.linux.setupLaunch(launchOptions, steamPath, bsFolderPath, env);
}
obs.next({type: BSLaunchEvent.BS_LAUNCHING});
+27 -17
View File
@@ -3,10 +3,11 @@ import path from "path";
import { writeFileSync } from "fs";
import { BSVersion } from "shared/bs-version.interface";
import { RequestService } from "./request.service";
import { pathExistsSync, readJSON } from "fs-extra";
import { readJSON } from "fs-extra";
import { allSettled } from "../../shared/helpers/promise.helpers";
import { LinuxService } from "./linux.service";
import { IS_FLATPAK } from "main/constants";
import { StaticConfigurationService } from "./static-configuration.service";
export class BSVersionLibService {
private readonly REMOTE_BS_VERSIONS_URL: string = "https://raw.githubusercontent.com/Zagrios/bs-manager/master/assets/jsons/bs-versions.json";
@@ -14,9 +15,10 @@ export class BSVersionLibService {
private static instance: BSVersionLibService;
private linuxService: LinuxService;
private utilsService: UtilsService;
private requestService: RequestService;
private readonly linuxService: LinuxService;
private readonly utilsService: UtilsService;
private readonly requestService: RequestService;
private readonly configService: StaticConfigurationService;
private bsVersions: BSVersion[];
@@ -24,6 +26,7 @@ export class BSVersionLibService {
this.linuxService = LinuxService.getInstance();
this.utilsService = UtilsService.getInstance();
this.requestService = RequestService.getInstance();
this.configService = StaticConfigurationService.getInstance();
}
public static getInstance(): BSVersionLibService {
@@ -37,25 +40,32 @@ export class BSVersionLibService {
return this.requestService.getJSON<BSVersion[]>(this.REMOTE_BS_VERSIONS_URL).then(res => res.data);
}
private async shouldLoadFromConfig(): Promise<boolean> {
// Some special cases of readonly memory installations
return IS_FLATPAK || this.linuxService.isNixOS();
}
private async getLocalVersions(): Promise<BSVersion[]> {
if (IS_FLATPAK) {
const flatpakVersionsPath = path.join(this.linuxService.getFlatpakLocalVersionFolder(), this.VERSIONS_FILE);
if (pathExistsSync(flatpakVersionsPath)) {
return readJSON(flatpakVersionsPath);
}
const localVersionsPath = path.join(this.utilsService.getAssestsJsonsPath(), this.VERSIONS_FILE);
if (!(await this.shouldLoadFromConfig())) {
return readJSON(localVersionsPath);
}
const localVersionsPath = path.join(this.utilsService.getAssestsJsonsPath(), this.VERSIONS_FILE);
return readJSON(localVersionsPath);
let versions = this.configService.get("versions");
if (!versions) {
versions = await readJSON(localVersionsPath)
}
return versions;
}
private async updateLocalVersions(versions: BSVersion[]): Promise<void> {
const localVersionsPath = path.join(
IS_FLATPAK
? this.linuxService.getFlatpakLocalVersionFolder()
: this.utilsService.getAssestsJsonsPath(),
this.VERSIONS_FILE
);
if (await this.shouldLoadFromConfig()) {
this.configService.set("versions", versions);
return;
}
const localVersionsPath = path.join(this.utilsService.getAssestsJsonsPath(), this.VERSIONS_FILE);
writeFileSync(localVersionsPath, JSON.stringify(versions, null, "\t"), { encoding: "utf-8", flag: "w" });
}
+30 -16
View File
@@ -1,12 +1,11 @@
import fs from "fs-extra";
import log from "electron-log";
import path from "path";
import { BS_APP_ID, PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants";
import { BS_APP_ID, IS_FLATPAK, PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants";
import { StaticConfigurationService } from "./static-configuration.service";
import { CustomError } from "shared/models/exceptions/custom-error.class";
import { BSLaunchError, LaunchOption } from "shared/models/bs-launch";
import { app } from "electron";
import config from "../../../electron-builder.config";
import { bsmExec } from "main/helpers/os.helpers";
export class LinuxService {
private static instance: LinuxService;
@@ -19,7 +18,9 @@ export class LinuxService {
}
private readonly staticConfig: StaticConfigurationService;
private protonCommand = "";
private protonPrefix = "";
private nixOS: boolean | undefined;
private constructor() {
this.staticConfig = StaticConfigurationService.getInstance();
@@ -27,7 +28,7 @@ export class LinuxService {
// === Launching === //
public setupLaunch(
public async setupLaunch(
launchOptions: LaunchOption,
steamPath: string,
bsFolderPath: string,
@@ -64,7 +65,10 @@ export class LinuxService {
BSLaunchError.PROTON_NOT_FOUND
);
}
this.protonCommand = `"${protonPath}" run`;
this.protonPrefix = await this.isNixOS()
? `steam-run "${protonPath}" run`
: `"${protonPath}" run`;
// Setup Proton environment variables
Object.assign(env, {
@@ -111,19 +115,29 @@ export class LinuxService {
return winePath;
}
public getProtonCommand(): string {
public getProtonPrefix(): string {
// Set in setupLaunch
return this.protonCommand;
return this.protonPrefix;
}
// === Flatpak Specific === //
// === NixOS Specific === //
public getFlatpakLocalVersionFolder(): string {
return path.join(
app.getPath("home"),
".var", "app", config.appId,
"resources", "assets", "jsons"
);
public async isNixOS(): Promise<boolean> {
if (this.nixOS !== undefined) {
return this.nixOS;
}
try {
await bsmExec("nixos-version", {
log: true,
flatpak: { host: IS_FLATPAK },
});
this.nixOS = true;
} catch (error) {
log.info("Not NixOS", error);
this.nixOS = false;
}
return this.nixOS;
}
}
@@ -4,6 +4,7 @@ import path from "path";
import { PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants";
import { Observable, Subject } from "rxjs";
import { CustomError } from "shared/models/exceptions/custom-error.class";
import { BSVersion } from "shared/bs-version.interface";
export class StaticConfigurationService {
private static instance: StaticConfigurationService;
@@ -90,6 +91,7 @@ export interface StaticConfigKeyValues {
// Linux Specific static configs
"proton-folder": string;
"versions": BSVersion[];
};
export type StaticConfigKeys = keyof StaticConfigKeyValues;