diff --git a/src/unity-editor.ts b/src/unity-editor.ts index 3c7e3de..3c4aba6 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -269,29 +269,26 @@ export class UnityEditor { throw new Error(`Cannot execute Unity ${this.version.toString()} on Apple Silicon Macs.`); } + const linuxEnvOverrides = process.platform === 'linux' + ? await this.prepareLinuxAudioEnvironment() + : undefined; + const baseEditorEnv: NodeJS.ProcessEnv = { + ...process.env, + UNITY_THISISABUILDMACHINE: '1', + ...(linuxEnvOverrides ?? {}) + }; + if (process.platform === 'linux' && !command.args.includes('-nographics') ) { - // On Linux, force Unity to run under Xvfb and provide a dummy audio driver - // to prevent FMOD from failing to initialize the output device when no - // actual audio device is present (common in CI/container environments). - const linuxEnv = { - ...process.env, - DISPLAY: ':99', - UNITY_THISISABUILDMACHINE: '1', - // Tell various audio systems to use a dummy/out-of-process driver - SDL_AUDIODRIVER: process.env.SDL_AUDIODRIVER || 'dummy', - AUDIODRIVER: process.env.AUDIODRIVER || 'dummy', - AUDIODEV: process.env.AUDIODEV || 'null', - // For PulseAudio: point to an invalid socket to avoid connecting - PULSE_SERVER: process.env.PULSE_SERVER || '/tmp/invalid-pulse-socket' - }; - unityProcess = spawn( 'xvfb-run', [this.editorPath, ...command.args], { stdio: ['ignore', 'ignore', 'ignore'], - env: linuxEnv + env: { + ...baseEditorEnv, + DISPLAY: baseEditorEnv.DISPLAY || ':99' + } }); } else if (process.arch === 'arm64' && process.platform === 'darwin' && @@ -311,10 +308,7 @@ export class UnityEditor { this.editorPath, command.args, { stdio: ['ignore', 'ignore', 'ignore'], - env: { - ...process.env, - UNITY_THISISABUILDMACHINE: '1' - } + env: baseEditorEnv }); } @@ -394,6 +388,43 @@ export class UnityEditor { return path.join(logsDir, `${prefix ? prefix + '-' : ''}Unity-${timestamp}.log`); } + private async prepareLinuxAudioEnvironment(): Promise { + if (process.platform !== 'linux') { + return {}; + } + + const envOverrides: NodeJS.ProcessEnv = { + SDL_AUDIODRIVER: process.env.SDL_AUDIODRIVER || 'dummy', + AUDIODRIVER: process.env.AUDIODRIVER || 'dummy', + AUDIODEV: process.env.AUDIODEV || 'null', + ALSA_CARD: process.env.ALSA_CARD || 'Loopback', + PULSE_SINK: process.env.PULSE_SINK || 'unity_dummy' + }; + + const defaultRuntimeDir = `/run/user/${typeof process.getuid === 'function' ? process.getuid() : 1000}`; + const runtimeDir = process.env.XDG_RUNTIME_DIR || defaultRuntimeDir; + envOverrides.XDG_RUNTIME_DIR = runtimeDir; + + try { + await fs.promises.mkdir(runtimeDir, { recursive: true, mode: 0o700 }); + } catch (error) { + this.logger.debug(`Failed to ensure XDG_RUNTIME_DIR (${runtimeDir}): ${error}`); + } + + await this.tryExec('bash', ['-c', 'pulseaudio --check 2>/dev/null || pulseaudio --start --exit-idle-time=-1 || true']); + await this.tryExec('bash', ['-c', 'command -v pactl >/dev/null 2>&1 && { pactl list short sinks 2>/dev/null | grep -q unity_dummy || pactl load-module module-null-sink sink_name=unity_dummy sink_properties=device.description=UnityCI >/tmp/unity-null-sink.id; } || true']); + + return envOverrides; + } + + private async tryExec(command: string, args: string[]): Promise { + try { + await Exec(command, args, { silent: true, showCommand: false }); + } catch (error) { + this.logger.debug(`Skipped helper command "${command} ${args.join(' ')}": ${error}`); + } + } + /** * Get the root path of the Unity Editor installation based on the provided editor path. * @param editorPath The path to the Unity Editor executable. diff --git a/src/unity-hub.ts b/src/unity-hub.ts index ba7cc67..c54e6f5 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -446,7 +446,7 @@ wget -qO - https://hub.unity3d.com/linux/keys/public | gpg --dearmor | tee /usr/ echo "deb [signed-by=/usr/share/keyrings/Unity_Technologies_ApS.gpg] https://hub.unity3d.com/linux/repos/deb stable main" > /etc/apt/sources.list.d/unityhub.list echo "deb https://archive.ubuntu.com/ubuntu jammy main universe" | tee /etc/apt/sources.list.d/jammy.list apt-get update -apt-get install -y --no-install-recommends unityhub${version ? '=' + version : ''} ffmpeg libgtk2.0-0 libglu1-mesa libgconf-2-4 libncurses5 +apt-get install -y --no-install-recommends unityhub${version ? '=' + version : ''} ffmpeg libgtk2.0-0 libglu1-mesa libgconf-2-4 libncurses5 pulseaudio apt-get clean sed -i 's/^\\(.*DISPLAY=:.*XAUTHORITY=.*\\)\\( "\\$@" \\)2>&1$/\\1\\2/' /usr/bin/xvfb-run printf '#!/bin/bash\nxvfb-run --auto-servernum /opt/unityhub/unityhub "$@" 2>/dev/null' | tee /usr/bin/unity-hub >/dev/null